Type Composition
4 type A struct {} 5 type B struct {} 6 func (_ A) Print() { fmt.Printf("A printed\n") } 7 func (_ B) Print() { fmt.Printf("B printed\n") } 8 func (a A) PrintA() { a.Print() } 9 type C struct { 10 A 11 *B 12 } 13 func main() { 14 var c C 15 c.B = &B{} 16 // Implicitly inherited 17 c.PrintA() 18 // Not allowed: amboguous 19 // c.Print() 20 // Explicitly disambiguated 21 c.B.Print() 22 c.A.Print() 23 }
From: compose.go
Go doesn’t support subclassing, but it is possible to achieve something similar via the limited form of implicit delegation that Go does support.
If you make an unnamed field in a Go structure, then any methods defined by the type of the field are implicitly added to the enclosing structure. The example at the start of this section contains a structure C that has two unnamed fields: fields with a type (A and B*), but with no name.
Note that the receiver for any of these methods will be the field, not the outer structure. This means that, even if the receiver is a pointer, it can not possible for it to access any of the fields in the outer structure. This can be slightly inconvenient. For example, it would be useful to be able to provide a List structure that could be added to any structure that needed list behavior, adding a Next() method returning the next item in the list, but this is not possible.
The example at the start of this section shows a problem that can occur when composing structures in this way: What happens when two inner structures implement the same methods? There are lots of ways of solving this problem, including priority schemes, which can get very complicated with multiple layers of nesting.
The Go solution is to make the programmer explicitly specify what they mean. Calling c.Print() in this example (the commented-out line) would cause the compiler to reject the program: it can’t figure out which Print() method you really mean without potentially introducing bugs into your program. You could extend this example by adding an explicit Print() method to C that delegated to one of the fields, or implemented the method in some other way.
Note that this example uses both a pointer and a value as fields, but the methods work on both. Exactly the same rules for methods apply in this case. The pointer field will add methods that take a value or a pointer, the value field will add methods that take the value.