在Go语言中使用嵌入代替继承

huangapple go评论92阅读模式
英文:

Embedding instead of inheritance in Go

问题

你对这个设计决策有什么意见?它有什么优点和缺点?

链接:

英文:

What is your opinion of this design decision? What advantages does it have and what disadvantages?

Links:

答案1

得分: 37

Gang of 4的关键原则是“优先使用组合而不是继承”;Go语言_让你遵循它_;-)。

英文:

The Gang of 4's crucial principle is "prefer composition to inheritance"; Go makes you follow it;-).

答案2

得分: 31

在一条评论中,你想知道嵌入的想法是否足以“完全取代继承”。我会说对于这个问题的答案是“是的”。几年前,我曾经简单地尝试过一个名为Snit的Tcl面向对象系统,它使用组合和委托来排除继承。Snit在很多方面与Go的方法有很大的不同,但在这一方面它们有一些共同的哲学基础。它是一种将功能和责任组合在一起的机制,而不是类的层次结构。

正如其他人所说,这实际上取决于语言设计者想要支持的编程实践。所有这些选择都有其优缺点;我不认为“最佳实践”这个词组在这里一定适用。我们可能会看到有人最终为Go开发一个继承层。

(对于熟悉Tcl的读者来说,我觉得Snit与该语言的“感觉”稍微更接近,至少在我看来,Tcl完全是关于委托的。)

英文:

In a comment, you wondered if the embedding idea was enough to "replace inheritance completely". I would say the answer to that question is "yes". A few years ago I played very briefly with a Tcl OO system called Snit, which used composition and delegation to the exclusion of inheritance. Snit is still vastly different from Go's approach, but in that one respect they have some common philosophical ground. It's a mechanism for joining together pieces of functionality and responsibility, not a hierarchy for the classes.

As others have stated, it's really about what kind of programming practices the language designers want to support. All such choices come with their own pros and cons; I don't think "best practices" is a phrase that necessarily applies here. We will probably see someone develop an inheritance layer for Go eventually.

(For any readers familiar with Tcl, I felt Snit to be a slightly closer match to the "feel" of the language than [incr Tcl] was. Tcl is all about the delegation, at least to my way of thinking.)

答案3

得分: 15

唯一真正使用继承的情况是:

  • 多态性

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

    • 这就是嵌入的用途

Go的方法并不完全一一对应,考虑一下在Java中继承和多态性的经典示例(基于此):

//在Java中(省略了许多不相关的细节)
//警告:根本不要使用,甚至不要用作测试

abstract class BankAccount
{
    int balance; //以分为单位
    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; //允许的负金额

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

在这里,继承和多态性被结合在一起,如果不改变底层结构,无法将其转换为Go。

我对Go的了解不是很深入,但我想它可能会像这样:

//大致上是Go?....不是吗?
//仅供说明目的;不太可能编译通过
//
//警告:这是完全错误的;这是在Go中编写Java

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("透支!")
    }
    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 //假设允许
}

type OverdraftAccount {
    *BankAccount
    overdraft int
}

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

根据注释,这是完全错误的编码方式,因为这是在Go中使用Java的方式。如果要在Go中编写这样的代码,它可能会与此有很大不同的组织方式。

英文:

The only real uses for inheritance are:

  • Polymorphism

    • Go's interface's "static duck typing" system solves this problem
  • Borrowing implementation from another class

    • This is what embedding is for

Go's approach doesn't exactly map 1-to-1, consider this classical example of inheritance and polymorphism in Java (based on this):

//roughly in Java (omitting lots of irrelevant details)
//WARNING: don&#39;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 &gt; 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;
    }
}

Here, inheritance and polymorphism are combined, and you can't translate this to Go without changing the underlying structure.

I haven't delved deeply into Go, but I suppose it would look something like this:

//roughly Go? .... no?
//for illustrative purposes only; not likely to compile
//
//WARNING: This is totally wrong; it&#39;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() &lt; amount {
        return errors.New(&quot;Overdraft!&quot;)
    }
    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&#39;s allowed
}

type OverdraftAccount {
    *BankAccount
    overdraft int
}

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

As per the note, this is totally a wrong way to code since one is doing Java in Go. If one was to write such a thing in Go, it would probably be organized a lot different than this.

答案4

得分: 7

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

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

这是一篇更深入探讨Go面向对象编程的文章:http://nathany.com/good

英文:

Embedding provides automatic delegation. This in itself isn't enough to replace inheritance, as embedding provides no form of polymorphism. Go interfaces do provide polymorphism, they are a bit different than the interfaces you may be use to (some people liken them to duck typing or structural typing).

In other languages, inheritance hierarchies need to be carefully designed because changes are wide sweeping and therefore hard to do. Go avoids these pitfalls while providing a powerful alternative.

Here's an article that delves into OOP with Go a little more: http://nathany.com/good

答案5

得分: 5

以下是翻译好的内容:

有人要求提供关于在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

英文:

Folks have requested links to information about embedding in Go.

Here's an "Effective Go" document where embedding is discussed and where concrete examples are provided.

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

The example makes more sense when you already have a good grasp of Go interfaces and types, but you can fake it by thinking of an interface as a name for a set of methods and if you think of a struct as similar to a C struct.

For more information on structs, you can see the Go language spec, which explicitly mentions nameless members of structs as embedded types:

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

So far I've only used it as a convenient way to put one struct in another without having to use a field name for the internal struct, when a field name wouldn't add any value to the source code. In the programming exercise below, I'm bundling a proposal type inside a type that has a proposal and a response channel.

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

答案6

得分: 3

我现在刚开始学习Go语言,但既然你要求一个意见,我会根据我目前所了解的知识给出一个意见。嵌入似乎是Go语言中许多其他事物的典型特征,它是对已经在现有语言中实践的最佳实践的显式语言支持。例如,正如Alex Martelli所指出的,四人帮说“优先使用组合而不是继承”。Go语言不仅移除了继承,而且使组合比C++/Java/C#中更容易、更强大。

我对类似“Go语言没有提供任何新的东西,我在其他语言中已经能做到了”和“为什么我们需要另一种语言?”这样的评论感到困惑。在某种意义上,我认为Go语言并没有提供任何以前不能通过一些工作完成的新东西,但在另一种意义上,新的是Go语言将促进并鼓励使用其他语言已经实践的最佳技术。

英文:

I am just now learning about Go, but since you are asking for an opinion, I'll offer one based on what I know so far. Embedding appears to be typical of many other things in Go, which is explicit language support for best practices that are already being done in existing languages. For example, as Alex Martelli noted, the Gang of 4 says "prefer composition to inheritance". Go not only removes inheritance, but makes composition easier and more powerful than in C++/Java/C#.

I've been puzzled by comments like "Go provides nothing new that I can't already do in language X," and "why do we need another language?" It appears to me that in one sense, Go doesn't provide anything new that couldn't be done before with some work, but in another sense, what is new is that Go will facilitate and encourage the use of the best techniques that are already in practice using other languages.

答案7

得分: 3

我喜欢它。

你使用的语言会影响你的思维模式。(只要问一个C程序员来实现“单词计数”,他们可能会使用链表,然后为了性能切换到二叉树。但是每个Java/Ruby/Python程序员都会使用字典/哈希表。语言已经对他们的大脑产生了如此大的影响,以至于他们无法考虑使用其他数据结构。)

使用继承时,你必须自上而下构建 - 从抽象的东西开始,然后将其子类化为具体的东西。你实际有用的代码将被埋藏在一个N层深的类中。这使得使用对象的“部分”变得困难,因为你不能重用代码而不引入父类。

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

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

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

英文:

I like it.

The language you use affects your thought patterns. (Just ask a C programmer to implement "word count". They will probably use a linked list, then switch to a binary tree for performance. But every Java/Ruby/Python programmer will use a Dictionary/Hash. The language has affected their brains so much that they can't think of using any other data structure.)

With inheritance, you have to build down -- start with the abstract thing, then subclass it to the specifics. Your actual useful code will be buried in a class N levels deep. This makes it hard to use a "part" of an object, because you can't re-use code without dragging in parent classes.

In Go, you can 'model' your classes this way (with interfaces). But you don't (can't) code this way.

Instead, you can use embedding. Your code can be broken up into small, isolated modules, each with their own data. This makes re-use trivial. This modularity has little to do with your "big" objects. (i.e. In Go, you can write a "quack()" method that doesn't even know about your Duck class. But in a typical OOP language, you can't declare "my Duck.quack() implementation has no dependencies on any other methods of Duck.")

In Go, this constantly forces the programmer to think about modularity. This leads to programs that have low coupling. Low coupling makes maintenance much easier. ("oh, look, Duck.quack() is really long and complex, but at least I know that it doesn't depend on the rest of Duck.")

huangapple
  • 本文由 发表于 2009年11月13日 13:13:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/1727250.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定