一尘不染

Go中是否存在脆弱的基类问题?

go

尽管使用了组合而不是继承?

如果是这样,在语言级别上有什么解决方案吗?


阅读 363

收藏
2020-07-02

共1个答案

一尘不染

正如VonC所写,但我想指出一点。

所述脆弱的基类问题通常归咎于虚拟方法
方法动态调度 -这意味着如果方法可重写的,其具有的实际实现在这样的重写方法只能在运行时被决定的情况下被调用)。

为什么这是个问题?您有一个类,向其中添加了一些方法,如果进行了MethodA()调用MethodB(),则不能保证MethodB()您所编写的将被调用,并且不能保证子类的其他方法可以覆盖您的MethodB()

在Go中有嵌入,但是没有多态性。如果将类型嵌入结构中,则嵌入类型的所有方法都将得到
提升,
并且将位于包装器结构类型的方法集中。但是您不能“替代”升级的方法。当然,您可以添加具有相同名称的自己的方法,然后在包装结构上以该名称调用方法将调用您的方法,但是如果从嵌入式类型调用此方法,则不会分派给您的方法,它将仍将调用已定义为嵌入式类型的“原始”方法。

因此,因此,我要说的是,脆弱的基类问题在Go语言中仅以相当缓解的形式存在。

演示Java中的问题

让我们来看一个例子。首先在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中 缺少 的问题

现在让我们看一下相同的示例,这次是用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()will call
Counter.IncBy(),它将调用Inc(),但这Inc()将是Counter.Inc(),因此这里没有无限循环。Counter甚至都不知道MyCounter,它没有对embedder
MyCounter值的任何引用。

2020-07-02