Idiomatic way to mimic proper dynamic dispatch in Go
Asked Answered
S

2

6

I am coming to Go from Java and some things confuses me.

For example, let's consider the following code:

package main

import (
    "fmt"
)

type I interface {
    Do()
    MegaDo()
}

type A struct {
}

func (a *A) Do() {
    fmt.Println("A")
}

func (a *A) MegaDo() {
    a.Do()
}

type B struct {
    A
}

func (a *B) Do() {
    fmt.Println("B")
}

var i I

func main() {
    fmt.Println("Hello, playground")

    var i I = &B{}

    i.MegaDo()
}

Here we have an interface I with methods Do() and MegaDo() . Struct A implements both methods and MegaDo calls Do internally. And B is composed over A and overrides only Do()

If I'll mimic the same code in Java I would expect it to print "B". But in Go it prints "A".

While I, kind of, understand why it happens (because it's embedding and not inheritance) I wonder how I can mimic the same thing in Go. For example I have two implementations of the same interface that differs only a little. How can I maximize code reusage in this case? I can't believe that in order to customize a bit logic in one implementation I have to copy-paste everything and just fix a little part in my code. Maybe there is some idiomatic way to do this in Go?

Sicken answered 5/3, 2018 at 15:37 Comment(1)
Does this answer your question? https://mcmap.net/q/1630748/-golang-dispatch-method-call-according-to-a-map-string-somestructDextrocular
A
12

Your question is too abstract to answer well, but I hope this helps.

Re-think the design starting with the real problem (business need etc) you're trying to solve, and don't try to solve it in Go using a Java design. Go has no inheritance and interfaces are its only form of polymorphism; you cannot "mimic dynamic dispatch" in Go in any reasonable way.

Specifically, in regards to this:

I have two implementations of the same interface that differs only a little. How can I maximize code reusage in this case? I can't believe that in order to customize a bit logic in one implementation I have to copy-paste everything and just fix a little part in my code.

Re-think your design in terms of code re-use rather than class hierarchy, because there is none. If you have two implementations of the same interface, that's fine! Go has interfaces and they work great. If you have a bunch of code repeated in both implementations, either a) abstract the shared code into functions that both implementations can call, or b) if the differences really are that small, maybe it should be a single implementation with some simple switching logic.

Abbreviation answered 5/3, 2018 at 15:43 Comment(0)
I
14

Go does not have subclassing or extension of "classes". Methods of embedded types use their original type receiver. In this case, the method MegaDo is promoted within B, but when called, it's called on the A field. B.MegaDo() is simply syntactical sugar for B.A.MegaDo(). Thus when it calls Do() on its receiver, it's calling the A version, not the B version.

The easier method of handling this is by embedding an interface. For example:

https://play.golang.org/p/ZPdK8zsy5_w

type Mega struct {
    I
}

func (m Mega) MegaDo() {
    m.Do()
} 

func main() {
    var a A
    var b B
    m := Mega{I: A}
    m.MegaDo()
    m.I = B
    m.MegaDo()
}

Note: embedding the interface isn't actually required in this case, as MegaDo() could simply call m.i.Do() if it were a named field. However, embedding it allows other code to directly call Do() on m, without knowing what type is actually embedded in that field. Also note that a practical upshot of embedding an interface is that the structure embedding the interface by definition fulfills that same interface as well.

Practical example of this pattern: a DB handle type that embeds the joint methods of a sql.DB and sql.Tx type (QueryRow, Query, Exec, etc). Users of that handle can call those methods without having to know whether they are being called within the context of a transaction or not.

Inebriety answered 5/3, 2018 at 15:44 Comment(0)
A
12

Your question is too abstract to answer well, but I hope this helps.

Re-think the design starting with the real problem (business need etc) you're trying to solve, and don't try to solve it in Go using a Java design. Go has no inheritance and interfaces are its only form of polymorphism; you cannot "mimic dynamic dispatch" in Go in any reasonable way.

Specifically, in regards to this:

I have two implementations of the same interface that differs only a little. How can I maximize code reusage in this case? I can't believe that in order to customize a bit logic in one implementation I have to copy-paste everything and just fix a little part in my code.

Re-think your design in terms of code re-use rather than class hierarchy, because there is none. If you have two implementations of the same interface, that's fine! Go has interfaces and they work great. If you have a bunch of code repeated in both implementations, either a) abstract the shared code into functions that both implementations can call, or b) if the differences really are that small, maybe it should be a single implementation with some simple switching logic.

Abbreviation answered 5/3, 2018 at 15:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.