0

I am planning to port our server to Go from c++ and I have a question with regards to handling a list of objects based from derived classes. What I meant is, say if I have a base class (or an interface) called A, and it has child classes B and C, and I want to have a generic list that can handle a list of Type A and it's derived.

In object oriented languages, I can just create a list of (pointer) A and that's it, I can add objects B and C to the list because they are indeed of type A. But since Go does not have inheritance, I am confused on how to effectively handle this.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
user2300947
  • 450
  • 4
  • 14
  • This comes up fairly frequently (https://stackoverflow.com/search?q=%5Bgo%5D+polymorphism); https://stackoverflow.com/questions/35115385/polymorphism-in-go-lang and https://stackoverflow.com/questions/53674376/understanding-polymorphism-in-go seem to answer your question (in short the answer is interfaces). – Brits Nov 25 '19 at 02:49

1 Answers1

0

As noted before, you can do this using interfaces. You can create a list of type []Intf where Intf is an interface common to your types, and then you can put any type that implements that interface into your list.

type A struct{}
type B struct{}

type Intf interface {
  // common methods of A and B
}

func f(a Intf) {
   // Here, a is A or B
}

You have to be careful about whether you're dealing with copies, or pointers. For instance:

a:=A{}
b:=B{}
x:=[]Intf{&a,&b}
f(x)

is different from:

x:=[]Intf{a,b}
f(x)

With the first one, if f modified the array elements, the variables a and b are modified because the interface contains a pointer. With the second one, if f modifies the array elements, a and b are not affected because the interfaces in the array contain pointers to copies of those variables.

If, say, A has a method included in the interface Intf that takes a pointer receiver, then you have to use the address of all A instances in the array.

Go's type system is explicit, and strict. So, for instance, you implemented your polymorphic list via interfaces, and you have functions:

func f(input []Intf) {
}
func g(input Intf) {
}

And let's say you have these variables:

var a []A
var b A

where A implements Intf. Then:

g(b) // This is valid
f(a) // This is *not* valid

This is because f gets a []Intf, but a is an []A, not []Intf. To call f, you have to build a []Intf:

fInput:=make([]Intf,0,len(a))
for _,x:=range a {
   fInput=append(fInput,x)
}
f(fInput)

So if you're dealing with polymorphic lists, then it makes sense to always work with the interface list, not the concrete type list.

You can also work with type assertions if you need direct access to fields depending on type:

func f(in []Intf) {
   for _,x:=range in {
      if a, ok:=x.(*A); ok {
          a.Field=1
      }
   }
}
Burak Serdar
  • 46,455
  • 3
  • 40
  • 59
  • I prefer to make an implementation an abstract class (as in C++), then specialize the abstract class to be different things, and then include it through composition in the class I am working on. This allows you to try polymorphism in a more specific way in your interface then in a general approach. – C. R. Ward Nov 27 '19 at 19:42