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

久未见 提交于 2020-01-15 10:14:47

问题


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:


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: Does fragile base class issue exist in Go? and here: Go embedded struct call child method instead parent method.

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.



来源:https://stackoverflow.com/questions/44526957/how-can-i-organise-this-go-code-that-redefines-methods-from-an-embedded-type-to

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!