How can I organise this Go code that redefines methods from an embedded type to be less redundant and more maintainable?

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

How can I organise this Go code that redefines methods from an embedded type to be less redundant and more maintainable?

问题

我有一些示例代码,其中我声明了一个名为foo的类型,它有一些相互调用的方法(比如foo.getfoo.doublefoo.toString调用)。

我还有另一个类型bar,它嵌入了foo并重新定义了get方法。我被迫在bar上重新定义doubletoString方法,这样它们就可以访问bar.get(而不仅仅是foo.get),但是这些函数的实现与原始代码基本相同。

有没有更好的方法来组织这段代码,以避免冗余,同时让bar实现与foo相同的接口?

注意:

  • 上述组织的代码是可以正常工作的;只是很难维护,因为当我重新定义一个最初在foo上声明的方法时,我必须仔细检查foo上的哪些其他方法调用了它,并确保重新定义所有这些方法。很容易不小心漏掉其中一个。
  • 在这个基于真实项目的代码中,有将近十几个类型嵌入了foo,每个类型上都有类似数量的相互连接的方法。
  • 我可以在foo上添加一个接口类型的成员,其中包含一个指向最外层嵌入它的结构体的指针,并且让foo.double调用f.outermost.get()而不是f.get(),但这似乎有点浪费,并且意味着foo的零值将无效。(而且只有当嵌入的类型是结构体时才可能实现这一点。)
英文:

I have some example code in which I declare a type foo with some methods which call each other (say: foo.get, which is called by foo.double and foo.toString).

I have another type, bar, which embeds foo and redefines get. I am forced to redefine double and toString on bar, so they can see bar.get (rather than only foo.get), but the body of these functions is basically identical to the original.

Is there a better way to organise this code, to avoid the redundancy while still having bar fulfill the same interfaces as foo?

Notes:

  • The code as organised above works fine; it's just difficult to maintain because when I go to redefine a method originally declared on foo on an type that embeds foo I have to check carefully to see which other methods on foo call it, and make sure to redefine all of those too. It has proven to be very easy to accidentally miss one.
  • In the real project on which this is based there are nearly a dozen types which embed 'foo', and a similar number of inter-connected methods on each type.
  • I could add an interface-valued member to foo containing a pointer to the outermost enclosing struct that embeds it, and have foo.double call f.outermost.get() instead of f.get(), but this seems a little wasteful and would mean that zero values of foo were not valid. (Plus it is only possible if the embedded type is a struct.)

答案1

得分: 3

在Go语言中,有嵌入的概念,但没有多态。如果在结构体中嵌入一个类型,嵌入类型的所有方法都会被提升,并且会成为包装结构体类型的方法集的一部分。但是你不能“覆盖”被提升的方法。当然,你可以添加一个同名的方法,在包装结构体上调用该方法会调用你自己的方法,但如果该方法是从嵌入类型中调用的,它将不会分派到你的方法,而是仍然调用嵌入类型中定义的“原始”方法。

在这里阅读更多信息:https://stackoverflow.com/questions/41819033/does-fragile-base-class-issue-exist-in-go/41827686#41827686 和 https://stackoverflow.com/questions/29390736/go-embedded-struct-call-child-method-instead-parent-method/29390981#29390981

看起来你只想“继承”double()toString()方法(在Go中应该叫做String()),而不是get()方法,因为它的实现因类型而异。

因此,你应该进行一些重构。你的foo类型应该有/获取一个提供get()方法的值。你可以使用一个getter接口来捕获这个值:

type getter interface {
    get() int
}

然后是foo的实现:

type foo struct {
    g getter
}

func (f foo) double() int {
    return f.g.get() * 2
}

func (f foo) toString() string {
    return fmt.Sprintf("%d", f.g.get())
}

还有一个bar类型,它嵌入了foo,并且只提供了“缺失”的get()方法:

type bar struct {
    foo
}

func (b bar) get() int {
    return 69
}

使用示例:

b := bar{}
b.foo = foo{g: b}
fmt.Println(b.double())
fmt.Println(b.toString())

输出如预期的那样(在Go Playground上尝试):

138
69

使用简单的函数值

使用上述的getter接口很好,因为它在将来提供了灵活性,如果你需要添加其他方法。

如果不是这种情况,而你只需要一个单独的函数,你可以省略接口,直接使用函数值。

代码如下所示:

type foo struct {
    get func() int
}

func (f foo) double() int {
    return f.get() * 2
}

func (f foo) toString() string {
    return fmt.Sprintf("%d", f.get())
}

type bar struct {
    foo
}

func (b bar) get() int {
    return 69
}

使用方法如下:

b := bar{}
b.foo = foo{get: b.get}
fmt.Println(b.double())
fmt.Println(b.toString())

输出结果相同。在Go Playground上尝试。

请注意,虽然我们使用了bar.get()方法(b.get 方法值),但并不要求get()必须是一个方法。它可以是一个普通的函数值,甚至是一个函数字面量,例如:

b := bar{foo: foo{get: func() int { return 69 }}}
fmt.Println(b.double())
fmt.Println(b.toString())

Go Playground上尝试这个例子。

英文:

In Go there is embedding, but there is no polymorphism. If you embed a type in a struct, all the methods of the embedded type get promoted and will be in the method set of the wrapper struct type. But you can't "override" the promoted methods. Sure, you can add your own method with the same name, and calling a method by that name on the wrapper struct will invoke your method, but if this method is called from the embedded type, that will not be dispatched to your method, it will still call the "original" method that was defined to the embedded type.

Read more about this here: https://stackoverflow.com/questions/41819033/does-fragile-base-class-issue-exist-in-go/41827686#41827686 and here: https://stackoverflow.com/questions/29390736/go-embedded-struct-call-child-method-instead-parent-method/29390981#29390981.

It looks like you only want to "inherit" the double() and toString() methods (which should be called String() in Go), but not the get() method as its implementation changes from type-to-type.

So basically you should refactor a little. Your foo type should have / get a value that provides the get() method. You can capture this with a getter interface:

type getter interface {
	get() int
}

And the foo implementation:

type foo struct {
	g getter
}

func (f foo) double() int {
	return f.g.get() * 2
}

func (f foo) toString() string {
	return fmt.Sprintf("%d", f.g.get())
}

And a bar type which embeds foo, and only provides the "missing" get():

type bar struct {
	foo
}

func (b bar) get() int {
	return 69
}

Usage example:

b := bar{}
b.foo = foo{g: b}
fmt.Println(b.double())
fmt.Println(b.toString())

Output is as expected (try it on the Go Playground):

138
69

With a simple function value

Using the above getter interface is nice, as it provides flexibility in the future, should you need to add other methods to it.

If this is not the case and all you need is a single function, you may leave out the interface and just use a function value.

This is how it could look like:

type foo struct {
	get func() int
}

func (f foo) double() int {
	return f.get() * 2
}

func (f foo) toString() string {
	return fmt.Sprintf("%d", f.get())
}

type bar struct {
	foo
}

func (b bar) get() int {
	return 69
}

And using it:

b := bar{}
b.foo = foo{get: b.get}
fmt.Println(b.double())
fmt.Println(b.toString())

Output is the same. Try it on the Go Playground.

Note that while we used the bar.get() method (b.get method value), it is not a requirement that get() should be a method. It can be an ordinary function value, or even a function literal, such as:

b := bar{foo: foo{get: func() int { return 69 }}}
fmt.Println(b.double())
fmt.Println(b.toString())

Try this one on the Go Playground.

huangapple
  • 本文由 发表于 2017年6月14日 00:28:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/44526957.html
匿名

发表评论

匿名网友

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

确定