英文:
How can I organise this Go code that redefines methods from an embedded type to be less redundant and more maintainable?
问题
我有一些示例代码,其中我声明了一个名为foo
的类型,它有一些相互调用的方法(比如foo.get
被foo.double
和foo.toString
调用)。
我还有另一个类型bar
,它嵌入了foo
并重新定义了get
方法。我被迫在bar
上重新定义double
和toString
方法,这样它们就可以访问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 embedsfoo
I have to check carefully to see which other methods onfoo
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
callf.outermost.get()
instead off.get()
, but this seems a little wasteful and would mean that zero values offoo
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论