尽管使用组合而不是继承?
如果是这样,在语言层面是否有任何解决方案?
答案 0 :(得分:8)
正如VonC所写,但我想指出一些事情。
fragile base class问题通常归咎于virtual methods(动态调度方法 - 这意味着如果方法可以被覆盖,那么必须调用的实际实现这种被覆盖的方法只能在运行时决定。
为什么这是一个问题?你有一个类,你添加了一些方法,如果MethodA()
调用MethodB()
,你就不能保证你所调用的MethodB()
会被调用而不是其他方法覆盖MethodB()
。
在Go中有embedding,但没有polymorphism。如果在结构中嵌入类型,则嵌入类型的所有方法都将提升,并且将位于包装器结构类型的method set中。但是你无法“覆盖”推广的方法。当然,您可以使用相同的名称添加自己的方法,并在包装器结构上调用该名称的方法将调用您的方法,但是如果从嵌入类型调用此方法,则不会将该方法调度到您的方法,仍将调用已定义为嵌入类型的“原始”方法。
因此,我认为脆弱的基类问题只在Go中以一种相当缓和的形式存在。
我们来看一个例子。首先是Java,因为Java“遭受”这种问题。让我们创建一个简单的Counter
类和一个MyCounter
子类:
class Counter {
int value;
void inc() {
value++;
}
void incBy(int n) {
value += n;
}
}
class MyCounter extends Counter {
void inc() {
incBy(1);
}
}
实例化并使用MyCounter
:
MyCounter m = new MyCounter();
m.inc();
System.out.println(m.value);
m.incBy(2);
System.out.println(m.value);
输出符合预期:
1
3
到目前为止一切顺利。现在,如果基类Counter.incBy()
将更改为:
void incBy(int n) {
for (; n > 0; n--) {
inc();
}
}
基类Counter
仍然完美且可操作。但MyCounter
出现故障:MyCounter.inc()
调用Counter.incBy()
,调用inc()
,但由于动态调度,它会调用MyCounter.inc()
...是的...无尽的循环。堆栈溢出错误。
现在让我们看一下相同的例子,这次是用Go:
写的type Counter struct {
value int
}
func (c *Counter) Inc() {
c.value++
}
func (c *Counter) IncBy(n int) {
c.value += n
}
type MyCounter struct {
Counter
}
func (m *MyCounter) Inc() {
m.IncBy(1)
}
测试它:
m := &MyCounter{}
m.Inc()
fmt.Println(m.value)
m.IncBy(2)
fmt.Println(m.value)
输出符合预期(在Go Playground上尝试):
1
3
现在让我们改变Counter.Inc()
,就像我们在Java示例中所做的那样:
func (c *Counter) IncBy(n int) {
for ; n > 0; n-- {
c.Inc()
}
}
运行完美,输出相同。在Go Playground上尝试。
这里发生的事情是MyCounter.Inc()
会调用Counter.IncBy()
来调用Inc()
,但Inc()
将Counter.Inc()
,所以这里没有无限循环。 Counter
甚至不知道MyCounter
,它没有任何对嵌入式MyCounter
值的引用。
答案 1 :(得分:2)
Fragile base class问题是,当派生类继承对基类进行看似安全的修改时,可能会导致派生类出现故障。
如上所述in this tutorial:
对于所有意图和目的,e mbedding an anonymous type的组合等同于实现继承。嵌入式结构与基类一样脆弱。