您对这个设计决策有何看法?它有什么优点和缺点?
链接:
Gang of 4的关键原则是“优先组合胜于继承”;Go让你跟随它;-)。
在评论中,您想知道嵌入的想法是否足以“完全取代继承”。我会说这个问题的答案是“是”。几年前,我非常简单地使用了一个名为Snit的 Tcl OO 系统,它使用组合和委托来排除继承。Snit 仍然与 Go 的方法有很大不同,但在这一方面它们有一些共同的哲学基础。它是一种将功能和职责连接在一起的机制,而不是类的层次结构。
正如其他人所说,这实际上是关于语言设计者想要支持什么样的编程实践。所有这些选择都有各自的优缺点;我不认为“最佳实践”是一个必然适用于此的短语。我们可能最终会看到有人为 Go 开发一个继承层。
(对于任何熟悉 Tcl 的读者,我觉得 Snit 比以前更接近于语言的“感觉”。Tcl 完全[incr Tcl]
是关于委托的,至少在我的思维方式上是这样。)
继承的唯一真正用途是:
多态性
从另一个类借用实现
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 中编写这样的东西,它的组织方式可能与此大不相同。
嵌入提供自动委托。这本身不足以取代继承,因为嵌入不提供任何形式的多态性。Go 接口确实提供了多态性,它们与您可能使用的接口有点不同(有些人将它们比作鸭子类型或结构类型)。
在其他语言中,继承层次结构需要仔细设计,因为更改范围很广,因此很难做到。Go 避免了这些陷阱,同时提供了强大的替代方案。
这是一篇深入研究 Go 的 OOP 的文章:http: //nathany.com/good
我刚刚学习围棋,但既然你在征求意见,我会根据我目前所知道的提供一个意见。嵌入似乎是 Go 中许多其他事物的典型特征,这是对已经在现有语言中完成的最佳实践的显式语言支持。例如,正如 Alex Martelli 所指出的,Gang of 4 说“更喜欢组合而不是继承”。Go 不仅消除了继承,而且使组合比 C++/Java/C# 更容易和更强大。
我一直对诸如“Go 没有提供我在语言 X 中做不到的新东西”和“为什么我们需要另一种语言?”之类的评论感到困惑。在我看来,从某种意义上说,Go 并没有提供任何以前通过某些工作无法完成的新事物,但在另一种意义上,Go 将促进和鼓励使用最好的技术已经在实践中使用其他语言。
人们已经请求了有关嵌入 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
我喜欢。
你使用的语言会影响你的思维模式。(只需让 C 程序员实现“字数统计”。他们可能会使用链表,然后切换到二叉树以提高性能。但是每个 Java/Ruby/Python 程序员都会使用字典/哈希。语言影响了他们的大脑如此之多,以至于他们无法想到使用任何其他数据结构。)
使用继承,您必须构建——从抽象事物开始,然后将其子类化为细节。您实际有用的代码将被深埋在 N 级深中。这使得很难使用对象的“部分”,因为如果不拖入父类就无法重用代码。
在 Go 中,您可以通过这种方式“建模”您的类(使用接口)。但是您不能(不能)以这种方式编码。
相反,您可以使用嵌入。你的代码可以分解成小的、独立的模块,每个模块都有自己的数据。这使得重用变得微不足道。这种模块化与您的“大”对象几乎没有关系。(即在 Go 中,您可以编写一个甚至不知道您的 Duck 类的“quack()”方法。但是在典型的 OOP 语言中,您不能声明“我的 Duck.quack() 实现不依赖于Duck 的任何其他方法。”)
在 Go 中,这不断迫使程序员考虑模块化。这导致程序耦合度低。低耦合使维护更容易。(“哦,看,Duck.quack() 真的很长很复杂,但至少我知道它不依赖于 Duck 的其余部分。”)