36

I want to provide a base struct with methods in my library that can be 'extended'.

The methods of this base struct rely on methods from the extending struct. This is not directly possible in Go, because struct methods only have acces to the structs own fields, not to parent structs.

The point is to have functionality that I do not have to repeat in each extending class.

I have come up with this pattern, which works fine, but looks quite convoluted due to it's cyclical structure.

I have never found anything like it in other Go code. Is this very un-go? What different approach could I take?

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
}
theduke
  • 3,027
  • 4
  • 29
  • 28
  • 16
    It's hard to tell what you're doing from a generic example, but this is not idiomatic Go code. In general, you need to avoid thinking about solutions via classes and inheritance altogether. Use composition to your advantage, and remember that not everything needs to be a struct represented by an interface -- sometimes functions are all you need. – JimB Aug 24 '15 at 18:14
  • 14
    Redesign your solution. Go has no inheritance. Trying to remodel inheritance with what Go provides most likely will fail. – Volker Aug 24 '15 at 18:20
  • 14
    Don't understand why you're being downvoted; you asked if this was an appropriate approach, and if not, what you could do to make it better. At the risk of looking like the odd guy out, I think your question is fine. With that being said, the two commenters above me hit it spot on. – william.taylor.09 Aug 24 '15 at 19:33
  • 1
    Your code example confuses me. You probably just want to define the 'common methods' on a single type and embed it in the types you want to offer those methods. You said you had a problem 'because struct methods only have acces to the structs own fields' but that's not really true. If you embed some type `Base` inside of types `A` and `B` then they can access an exported field for `Base` inside their methods directly. If `Base` has a method `One` and you want to override it you can redefine it in `A` and still call `A.Base.One()` from in that context just like you would in those OO languages. – evanmcdonnal Aug 24 '15 at 22:43
  • 2
    I think you need to give a more concrete example of what you're trying to do and why you think this approach is necessary. I gave you an upvote to neutralize the rating because I agree with @william.taylor.09. – user3591723 Aug 25 '15 at 01:42
  • you need struct embedding feature of golang. check this http://stackoverflow.com/questions/32125832/after-renaming-a-type-i-cannot-access-some-of-its-methods/ vote if you like the answer. – Jiang YD Aug 25 '15 at 03:05
  • Go `does` have inheritance, it's called embedding and behaves the same (or almost) https://golang.org/doc/effective_go.html#embedding – Petruza Sep 25 '19 at 13:23
  • Pro: Go makes embedding act like inheritance (no need to re-declare methods and implement by delegating to struct members like you have to in most O-O languages, e.g. Java). Con: The compiler won't tell me if I have or have not successfully implemented a given interface. Con: explicit "implements Foo" is very useful to us humans trying to understand the code. – Charlie Reitzel Apr 13 '22 at 15:34
  • I push back on the "composition is always better than inheritance". Yes, inheritance can be abused. But it is a tool like any other. It is the right tool for a whole bunch of jobs in software. Composition is another tool. Go has blurred the lines between the two ... – Charlie Reitzel Apr 13 '22 at 15:36

2 Answers2

41

As mentioned in people's comments, Go encourages composition over inheritance.

To address your question about reducing code duplication, you would want to use embedding.

Using the example from Effective Go linked above, you start with very narrow interfaces that only do a few things:

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

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

Then you can either compose interfaces together into another interface:

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

It works similarly for structs, where you can compose structs that implement Reader and Writer together in another struct:

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
}

Basically, anything that implements a Reader or a Writer can be reused by composing them together in a struct, and that outer struct will automatically implement the ReadWriter interface.

This is basically doing Dependency Injection, and it's super useful for testing too.

Example from the struct code above:

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()
}

One thing to point out that's slightly different from other languages' composition paradigm is that the MyReadWriter struct can now act as both a Reader and a Writer. That's why in DoCrazyStuff() we do rw.Read(data) instead of rw.Reader.Read(data).

UPDATE: Fixed incorrect example.

Ross
  • 1,013
  • 14
  • 32
Addison
  • 1,065
  • 12
  • 17
  • 2
    Except you almost never want to use a pointer to an interface. Your `MyReadWriter` should **not** be using pointers to interfaces but should instead be `type MyReadWriter struct { Reader, Writer }`. That allows for (e.g.) `rw := MyReadWriter{ strings.NewReader("foo"), new(bytes.Buffer) }`. – Dave C Sep 04 '15 at 15:40
  • Thanks for the heads up, that's good to know. Do you have a link that explains why? Also, this example is straight from the effective go page. – Addison Sep 05 '15 at 01:04
  • 2
    A good place to get some more details about interfaces is [a blog article by Russ Cox](http://research.swtch.com/interfaces). – Dave C Sep 05 '15 at 01:59
  • I had to re-read [the relevant section](https://golang.org/doc/effective_go.html#embedding) carefully to notice the context that you lost. The first example is showing the definition of `io.Reader` and `io.Writer` which are interfaces (and there also is an [`io.ReadWriter`](https://golang.org/pkg/io#ReadWriter)). The later is showing `*bufio.Reader` and `*bufio.Writer` which are (both pointers to struct types) being embedded into a type to implement `io.ReadWriter`. Those two excerpts come from different packages and you can't just cut-n-paste them together. – Dave C Sep 05 '15 at 02:03
  • 1
    @DaveC, you're right! I updated the answer with fixed examples. – Addison Nov 27 '15 at 09:39
  • Your edits have lost an "either—or", replacing it with a hanging "either" that doesn't connect to any later text. Confusing. – Wildcard May 08 '20 at 22:38
4

Sorry to disappoint you, but you are asking the wrong question. I had a similar problem when I started writing Go code.

You can not simply take a class hierarchy and translate it to Go code, at least not with satisfying results. Usually there is a very elegant and simple way to solve such things in Go, but to discover them, you need to think a bit differently as you are used to.

Unfortunately, your question doesn't say anything about what problem you are trying to solve. You have just described how you would like to solve it. Therefore I am a bit reluctant to give a general answer, since it will not lead to idiomatic Go code. I understand if you are disappointed by that answer, but in my opinion, that`s the most value-able answer you can get :)

tux21b
  • 90,183
  • 16
  • 117
  • 101