Go 中是否存在 fragile base class 问题?
Does fragile base class issue exist in Go?
尽管使用组合而不是继承?
如果是,有没有语言层面的解决方案?
Fragile base class 问题是当对基础 class 的看似安全的修改被派生的 classes 继承时,可能会导致派生的 classes故障。
如前所述in this tutorial:
For all intents and purposes, composition by embedding an anonymous type is equivalent to implementation inheritance. An embedded struct is just as fragile as a base class.
正如 VonC 所写,但我想指出一些事情。
fragile base class problem is often blamed on virtual methods (方法的动态分配 – 这意味着如果方法可以被覆盖,则在这种被覆盖的方法的情况下必须调用的实际实现只能在运行时决定。
为什么这是个问题?你有一个class,你给它添加一些方法,如果MethodA()
调用MethodB()
,你不能保证你写的MethodB()
会被调用并且不是 subclass 的其他方法覆盖你的 MethodB()
.
在 Go 中有 embedding, but there is no polymorphism. If you embed a type in a struct, all the methods of the embedded type get promoted and will be in the method set 包装器结构类型。但是你不能 "override" 提升的方法。当然,您可以添加自己的同名方法,并在包装器结构上调用该名称的方法将调用您的方法,但如果从嵌入式类型调用此方法,则不会将其分派给您的方法,它仍将调用定义到嵌入类型的 "original" 方法。
因此,我认为脆弱的基础 class 问题在 Go 中仅以相当缓和的形式存在。
例子
演示 Java
中的问题
让我们看一个例子。先在Java,因为Java"suffers"这种问题。让我们创建一个简单的 Counter
class 和一个 MyCounter
subclass:
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
到目前为止一切顺利。现在,如果基数 class、Counter.incBy()
将更改为:
void incBy(int n) {
for (; n > 0; n--) {
inc();
}
}
基地 class 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
现在让我们像在 Java 示例中所做的那样更改 Counter.Inc()
:
func (c *Counter) IncBy(n int) {
for ; n > 0; n-- {
c.Inc()
}
}
运行完美,输出是一样的。在 Go Playground.
上试试
这里发生的是 MyCounter.Inc()
会调用 Counter.IncBy()
,Counter.IncBy()
会调用 Inc()
,但是这个 Inc()
会是 Counter.Inc()
,所以不会死循环这里。 Counter
甚至不知道 MyCounter
,它没有任何对嵌入器 MyCounter
值的引用。
尽管使用组合而不是继承?
如果是,有没有语言层面的解决方案?
Fragile base class 问题是当对基础 class 的看似安全的修改被派生的 classes 继承时,可能会导致派生的 classes故障。
如前所述in this tutorial:
For all intents and purposes, composition by embedding an anonymous type is equivalent to implementation inheritance. An embedded struct is just as fragile as a base class.
正如 VonC 所写,但我想指出一些事情。
fragile base class problem is often blamed on virtual methods (方法的动态分配 – 这意味着如果方法可以被覆盖,则在这种被覆盖的方法的情况下必须调用的实际实现只能在运行时决定。
为什么这是个问题?你有一个class,你给它添加一些方法,如果MethodA()
调用MethodB()
,你不能保证你写的MethodB()
会被调用并且不是 subclass 的其他方法覆盖你的 MethodB()
.
在 Go 中有 embedding, but there is no polymorphism. If you embed a type in a struct, all the methods of the embedded type get promoted and will be in the method set 包装器结构类型。但是你不能 "override" 提升的方法。当然,您可以添加自己的同名方法,并在包装器结构上调用该名称的方法将调用您的方法,但如果从嵌入式类型调用此方法,则不会将其分派给您的方法,它仍将调用定义到嵌入类型的 "original" 方法。
因此,我认为脆弱的基础 class 问题在 Go 中仅以相当缓和的形式存在。
例子
演示 Java
中的问题让我们看一个例子。先在Java,因为Java"suffers"这种问题。让我们创建一个简单的 Counter
class 和一个 MyCounter
subclass:
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
到目前为止一切顺利。现在,如果基数 class、Counter.incBy()
将更改为:
void incBy(int n) {
for (; n > 0; n--) {
inc();
}
}
基地 class 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
现在让我们像在 Java 示例中所做的那样更改 Counter.Inc()
:
func (c *Counter) IncBy(n int) {
for ; n > 0; n-- {
c.Inc()
}
}
运行完美,输出是一样的。在 Go Playground.
上试试这里发生的是 MyCounter.Inc()
会调用 Counter.IncBy()
,Counter.IncBy()
会调用 Inc()
,但是这个 Inc()
会是 Counter.Inc()
,所以不会死循环这里。 Counter
甚至不知道 MyCounter
,它没有任何对嵌入器 MyCounter
值的引用。