Golang和继承

36

我希望在我的库中提供一个基础结构和方法,可以通过“扩展”来实现。

这个基础结构的方法依赖于扩展结构中的方法。 在Go语言中,这不是直接可能的,因为结构体方法只能访问自己的字段,不能访问父级结构体。

关键在于拥有功能,而无需在每个扩展类中都重复编写。

我想出了这个模式,它运行良好,但由于其循环结构而看起来相当复杂。

我从未在其他Go代码中找到过类似的东西。 这是否非常不符合Go语言的特点? 我可以采用什么不同的方法?

type MyInterface interface {
  SomeMethod(string)
  OtherMethod(string)
}

type Base struct{
  B MyInterface
}

func (b *Base) SomeMethod(x string) {
  b.B.OtherMethod(x)
}

type Extender struct {
  Base
}

func (b *Extender) OtherMethod(x string) {
  // Do something...
}

func NewExtender() *Extender { 
  e := Extender{}
  e.Base.B = &e
  return &e
}

16
从一个通用的例子很难判断你正在做什么,但这不是符合Go代码规范的。一般来说,你需要完全避免通过类和继承来考虑解决方案。利用组合来优化代码,并记住并非所有东西都需要由接口表示的结构体 - 有时候函数就足够了。 - JimB
14
重新设计你的解决方案。Go语言没有继承机制。试图用Go提供的东西来模拟继承很可能会失败。 - Volker
14
不明白为什么有人会对你点踩;你问这种方式是否合适,如果不合适,你应该怎么做才能让它更好。冒着显得格格不入的风险,我认为你的问题很好。话虽如此,上面两位评论者说得很到点子上。 - william.taylor.09
1
你的代码示例让我感到困惑。你可能只是想在一个类型上定义“公共方法”,并将其嵌入到你想要提供这些方法的类型中。你说你有一个问题,“因为结构体方法只能访问结构体自己的字段”,但这并不完全正确。如果你在类型AB中嵌入了一些类型Base,那么它们可以直接在它们的方法中访问Base的导出字段。如果Base有一个方法One,而你想要重写它,你可以在A中重新定义它,并且仍然可以从那个上下文中调用A.Base.One(),就像在那些面向对象的语言中一样。 - evanmcdonnal
2
我认为你需要给出一个更具体的例子,说明你想做什么以及为什么你认为这种方法是必要的。我给了你一个赞成票来中和评分,因为我同意@william.taylor.09的观点。 - user3591723
显示剩余4条评论
2个回答

41

如评论中所述,Go鼓励组合而非继承。

为了解决您关于减少代码重复的问题,您应该使用嵌入

使用上面链接到的Effective Go示例,您可以从仅执行少量操作的非常狭窄接口开始:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

然后你可以将接口组合在一起形成另一个接口:

// ReadWriter is the interface that combines the Reader and Writer interfaces.
type ReadWriter interface {
    Reader
    Writer
}

对于结构体,它的工作方式类似,您可以将实现Reader和Writer接口的结构体组合到另一个结构体中:

type MyReader struct {}
func (r *MyReader) Read(p []byte) (n int, err error) {
    // Implements Reader interface.
}
type MyWriter struct {}
func (w *MyWriter) Write(p []byte) (n int, err error) {
    // Implements Writer interface.
}

// MyReadWriter stores pointers to a MyReader and a MyWriter.
// It implements ReadWriter.
type MyReadWriter struct {
    *MyReader
    *MyWriter
}

基本上,任何实现了 Reader 或者 Writer 的可以通过在一个结构体中组合它们来重用,并且外部的这个结构体会自动实现 ReadWriter 接口。

这基本上就是在进行 依赖注入,而且对于测试也非常有用。

以下是上面结构体代码的示例:

func (rw *MyReadWriter) DoCrazyStuff() {
    data := []byte{}
    // Do stuff...
    rw.Read(data)
    rw.Write(data)
    // You get the idea...
}

func main() {
    rw := &MyReadWriter{&MyReader{}, &MyWriter{}}
    rw.DoCrazyStuff()
}

需要指出的一件事,与其他语言的组合范例略有不同的是,MyReadWriter 结构体现在可以同时充当 ReaderWriter。这就是为什么在 DoCrazyStuff() 中我们使用 rw.Read(data) 而不是 rw.Reader.Read(data)

更新:已修复示例错误。


2
除非你几乎从不想使用指向接口的指针。你的 MyReadWriter 不应该使用指向接口的指针,而应该是 type MyReadWriter struct { Reader, Writer }。这允许(例如)rw := MyReadWriter{ strings.NewReader("foo"), new(bytes.Buffer) } - Dave C
谢谢提醒,这很有用。你有解释为什么的链接吗?此外,这个例子直接来自有效的 Go 页面。 - Addison
2
一个了解接口更多细节的好地方是Russ Cox的博客文章 - Dave C
我不得不仔细重新阅读相关章节才能注意到您遗失的上下文。第一个示例展示了io.Readerio.Writer的定义,它们是接口(还有一个io.ReadWriter)。后面的示例显示了*bufio.Reader*bufio.Writer,它们都是被嵌入到一种类型中以实现io.ReadWriter的结构体类型指针。这两个摘录来自不同的程序包,不能简单地将它们剪切并粘贴在一起。 - Dave C
1
@DaveC,你说得对!我已经更新了答案,并修正了示例。 - Addison
您的编辑中丢失了一个“要么...要么”,用一个悬空的“要么”替换了它,这样就无法连接到任何后面的文本。令人困惑。 - Wildcard

4
抱歉让您失望了,但您问错了问题。当我开始撰写Go代码时,我也遇到了类似的问题。
您不能简单地将一个类层次结构翻译成Go代码,至少不能得到令人满意的结果。通常在Go中解决这些问题有一种非常优美和简单的方法,但要发现它们,您需要以不同于您所习惯的方式进行思考。
不幸的是,您的问题并没有说明您要解决的具体问题。您只是描述了您希望如何解决问题。因此,我有点不愿意给出一个通用答案,因为它不会导致符合惯用的Go代码。我理解如果您对这个答案感到失望,但在我看来,这是您可以获得的最有价值的答案 :)

10
这是评论,而不是对问题的回答。 - Rob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接