在Go中嵌入而不是继承

时间:2009-11-13 05:13:04

标签: inheritance go embedding

您对此设计决定有何看法?它有什么优点和缺点?

链接:

7 个答案:

答案 0 :(得分:36)

Gang of 4的关键原则是“更喜欢构成继承”;去制作你遵循它; - )。

答案 1 :(得分:29)

在评论中,您想知道嵌入的想法是否足以“完全替换继承”。我会说这个问题的答案是肯定的。几年前,我使用名为Snit的Tcl OO系统进行了非常简短的介绍,该系统使用组合和委托来排除继承。 Snit与Go的方法仍有很大不同,但在这方面,他们有一些共同的哲学基础。它是一种将功能和责任连接在一起的机制,而不是类的层次结构。

正如其他人所说,它实际上是关于语言设计者想要支持哪种编程实践。所有这些选择都有各自的优点和缺点;我不认为“最佳实践”是一个必然适用的短语。我们可能会看到有人为Go最终开发了一个继承层。

(对于任何熟悉Tcl的读者来说,我觉得Snit与[incr Tcl]的语言“感觉”略微接近.Tcl是关于代表团的,至少是我的思维方式。)

答案 2 :(得分:12)

继承的唯一真正用途是:

  • 多态性

    • Go的界面“静态鸭子打字”系统解决了这个问题
  • 借用另一个类的实现

    • 这就是
    • 的嵌入

Go的方法并不完全映射1对1,请考虑Java(based on this)中继承和多态的经典示例:

//roughly in Java (omitting lots of irrelevant details)
//WARNING: don't use at all, not even as a test

abstract class BankAccount
{
    int balance; //in cents
    void Deposit(int money)
    {
        balance += money;
    }

    void withdraw(int money)
    {
        if(money > maxAllowedWithdrawl())
            throw new NotEnoughMoneyException();
        balance -= money;
    }

    abstract int maxAllowedWithdrawl();
}

class Account extends BankAccount
{
    int maxAllowedWithdrawl()
    {
        return balance;
    }
}

class OverdraftAccount extends BankAccount
{
    int overdraft; //amount of negative money allowed

    int maxAllowedWithdrawl()
    {
        return balance + overdraft;
    }
}

这里,继承和多态组合在一起,你不能在不改变底层结构的情况下将它转换为Go。

我没有深入研究Go,但我认为它看起来像这样:

//roughly Go? .... no?
//for illustrative purposes only; not likely to compile
//
//WARNING: This is totally wrong; it's programming Java in Go

type Account interface {
    AddToBalance(int)
    MaxWithdraw() int
}

func Deposit(account Account, amount int) {
    account.AddToBalance(amount)
}

func Withdraw(account Account, amount int) error {
    if account.MaxWithdraw() < amount {
        return errors.New("Overdraft!")
    }
    account.AddToBalance(-amount)
    return nil
}

type BankAccount {
    balance int
}

func (account *BankAccount) AddToBalance(amount int) {
    account.balance += amount;
}

type RegularAccount {
    *BankAccount
}

func (account *RegularAccount) MaxWithdraw() int {
    return account.balance //assuming it's allowed
}

type OverdraftAccount {
    *BankAccount
    overdraft int
}

func (account *OverdraftAccount) MaxWithdraw() int {
    return account.balance + account.overdraft
}

根据说明,这是完全错误的代码方式,因为一个人在Go中使用Java。如果要在Go中编写这样的东西,它可能会组织起来与此不同。

答案 3 :(得分:7)

嵌入提供自动委派。这本身不足以替换继承,因为嵌入不提供任何形式的多态。 Go接口确实提供了多态性,它们与您可能使用的接口有点不同(有些人将它们比作鸭子打字或结构类型)。

在其他语言中,需要仔细设计继承层次结构,因为更改范围广泛,因此很难做到。 Go避免了这些陷阱,同时提供了强大的替代方案。

这篇文章深入研究了OOP和Go:http://nathany.com/good

答案 4 :(得分:3)

我刚刚开始学习Go,但既然你在征求意见,我会根据目前的知识提供一个意见。嵌入似乎是Go中许多其他内容的典型,它是对现有语言中已经完成的最佳实践的显式语言支持。例如,正如Alex Martelli所指出的那样,Gang of 4表示“更喜欢组合继承”。 Go不仅删除了继承,而且使组合比C ++ / Java / C#更容易,更强大。

我一直对以下评论感到困惑:“Go没有提供任何我在语言X中无法做到的新内容”,以及“为什么我们需要另一种语言?”在我看来,从某种意义上说,Go并没有提供任何在做某些工作之前无法做到的新事物,但从另一个意义上说,新的是Go将促进和鼓励使用最好的技术。已经在实践中使用其他语言。

答案 5 :(得分:3)

人们要求链接到有关嵌入Go的信息。

这是一个“Effective Go”文档,其中讨论了嵌入,并提供了具体示例。

http://golang.org/doc/effective_go.html#embedding

当你已经掌握了Go接口和类型时,这个例子更有意义,但你可以通过将接口视为一组方法的名称并且如果你认为一个类似于a的结构来伪造它C struct。

有关结构的更多信息,您可以看到Go语言规范,它明确地将结构的无名成员称为嵌入类型:

http://golang.org/ref/spec#Struct_types

到目前为止,我只使用它作为一种方便的方法将一个结构放在另一个结构中而不必使用内部结构的字段名称,而字段名称不会向源代码添加任何值。在下面的编程练习中,我将提案类型捆绑在具有提案和响应渠道的类型中。

https://github.com/ecashin/go-getting/blob/master/bpaxos.go#L30

答案 6 :(得分:3)

我喜欢它。

您使用的语言会影响您的思维模式。 (只要求C程序员实现“字数统计”。他们可能会使用链表,然后切换到二叉树以获得性能。但是每个Java / Ruby / Python程序员都会使用Dictionary / Hash。这种语言影响了他们的大脑太多,以至于他们无法想到使用任何其他数据结构。)

使用继承,你必须构建 - 从抽象的东西开始,然后将其子类化为细节。您实际有用的代码将被隐藏在N级深层。这使得很难使用对象的“部分”,因为如果不拖动父类,就无法重用代码。

在Go中,您可以通过这种方式“模拟”您的类(使用接口)。但你不能(不能)以这种方式编码。

相反,您可以使用嵌入。您的代码可以分解为小的,独立的模块,每个模块都有自己的数据。这使得重复使用变得微不足道。这种模块化与你的“大”物体几乎没有关系。 (即在Go中,你可以编写一个甚至不知道你的Duck类的“quack()”方法。但是在典型的OOP语言中,你不能声明“我的Duck.quack()实现没有依赖性Duck的任何其他方法。“)

在Go中,这不断迫使程序员考虑模块化。这导致程序具有低耦合。低耦合使维护更容易。 (“哦,看,Duck.quack()真的很长很复杂,但至少我知道它不依赖于Duck的其余部分。”)