53

您对这个设计决策有何看法?它有什么优点和缺点?

链接:

4

7 回答 7

36

Gang of 4的关键原则是“优先组合胜于继承”;Go你跟随它;-)。

于 2009-11-13T05:20:02.577 回答
30

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

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

(对于任何熟悉 Tcl 的读者,我觉得 Snit 比以前更接近于语言的“感觉”。Tcl 完全[incr Tcl]是关于委托的,至少在我的思维方式上是这样。)

于 2009-11-19T06:58:29.707 回答
12

继承的唯一真正用途是:

  • 多态性

    • Go 接口的“静态鸭子类型”系统解决了这个问题
  • 从另一个类借用实现

    • 这就是嵌入的目的

Go 的方法并不精确地映射 1 对 1,考虑这个 Java 中继承和多态的经典示例(基于 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 中编写这样的东西,它的组织方式可能与此大不相同。

于 2009-11-13T07:42:49.927 回答
7

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

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

这是一篇深入研究 Go 的 OOP 的文章:http: //nathany.com/good

于 2013-03-07T16:26:12.157 回答
3

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

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

于 2009-11-18T15:46:41.647 回答
3

人们已经请求了有关嵌入 Go 的信息的链接。

这是一个“Effective 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

于 2012-12-22T02:46:53.320 回答
3

我喜欢。

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

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

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

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

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

于 2014-01-19T04:42:41.383 回答