代码之家  ›  专栏  ›  技术社区  ›  kervin

在go中嵌入而不是继承

  •  52
  • kervin  · 技术社区  · 5 年前

    你对这个设计决定有什么看法?它有哪些优点和缺点?

    链接:

    7 回复  |  直到 11 年前
        1
  •  29
  •   Zac Thompson    15 年前

    在一条评论中,您想知道嵌入思想是否足以“完全替换继承”。我会说这个问题的答案是“是的”。几年前,我在一个名为TCL OO的系统上玩了很短的一段时间。 Snit 使用组合和委托排除继承。SNIT仍然与Go的方法大不相同,但在这一方面,他们有一些共同的哲学基础。它是一种将功能和责任结合在一起的机制,而不是类的层次结构。

    正如其他人所说,它实际上是关于语言设计者希望支持哪种编程实践。所有这些选择都有各自的利弊;我认为“最佳实践”在这里并不一定适用。我们可能会看到最终会有人开发一个继承层。

    (对于任何熟悉TCL的读者来说,我觉得SNIT与语言的“感觉”比 [incr Tcl] 是。TCL是代表团的一切,至少在我看来是这样。)

        2
  •  36
  •   Alex Martelli    15 年前

    这个 Gang of 4 其关键原则是“宁可组合,不可继承”; 制造 你跟着它走;-)。

        3
  •  12
  •   hasen j    11 年前

    继承的唯一真正用途是:

    • 多态性

      • 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
    }
    

    按照说明,这是完全错误的代码方式,因为一个在Java中运行Java。如果一个人要在围棋里写这样的东西,那么它的组织结构可能与此大不相同。

        4
  •  7
  •   nathany    11 年前

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

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

    以下是一篇深入探讨OOP和Go的文章: http://nathany.com/good

        5
  •  3
  •   Greg Graham    15 年前

    我现在正在学习围棋,但既然你在征求意见,我会根据我目前所知提供一个。嵌入似乎是Go中许多其他事情的典型特征,这是对现有语言中已经完成的最佳实践的显式语言支持。例如,正如亚历克斯·马泰利所指出的,四人帮说“喜欢组合而不是继承”。GO不仅去除了继承,而且使构图更容易和更强大。

    “Go提供了我在语言X中无法做到的新功能”,“为什么我们需要另一种语言?”这样的评论让我感到困惑。在我看来,在某种意义上,Go并没有提供任何以前无法完成的新功能,但在另一个意义上,Go的新功能是促进和鼓励使用已经在实践中使用其他语言的最佳技术。

        6
  •  3
  •   Ed Cashin    12 年前

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

    这里有一个“有效的执行”文档,讨论了嵌入,并提供了具体的示例。

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

    当您已经很好地掌握了Go接口和类型时,这个例子就更有意义了,但是如果您认为一个接口是一组方法的名称,并且您认为一个结构类似于一个C结构,那么您可以将它伪装成一个接口。

    有关结构的详细信息,可以看到Go语言规范,其中明确提到结构的无名称成员作为嵌入类型:

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

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

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

        7
  •  3
  •   BraveNewCurrency    11 年前

    我喜欢它。

    你使用的语言会影响你的思维模式。(只需让C程序员实现“字数统计”。他们可能会使用链表,然后切换到二叉树以获得性能。但是每一个Java/Ruby/Python程序员都会使用字典/散列。这种语言对他们的大脑影响如此之大,以至于他们无法想到使用任何其他的数据结构。)

    对于继承,您必须构建——从抽象的东西开始,然后将其子类化为特定的东西。您真正有用的代码将被隐藏在n级深度中。这使得很难使用对象的“部分”,因为不在父类中拖动就不能重用代码。

    在Go中,您可以用这种方式(通过接口)对类进行建模。但你不能这样编码。

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

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