DMarlon Studio

Lesson 3 · 3 of 20

Embedding and composition · Go

Compose types in Go by embedding structs: promoted fields and methods, overriding a method, and why it is not inheritance. Customers and addresses.

Go · Embedding and composition in DMarlon Studio: the example's code and its output in the terminal

Go has no inheritance; it has composition. Customer includes Contact and Address by writing just the type, with no field name: that is an embedded field. The field exists and is named after its type, c.Contact and c.Address.

The fields and methods of the embedded type are promoted: c.Name is really c.Contact.Name and c.City is c.Address.City. The same goes for methods: c.Label() and c.Line() work as if Customer had declared them, so Customer satisfies any interface that asks for Label() string.

Promotion goes through several levels. VIP embeds Customer, so v.Points += 50 changes v.Customer.Points (from 120 to 170) and v.Contact.Email reaches the contact two levels down.

VIP declares its own Label, which shadows the promoted one: v.Label() adds the tier, [gold]. The original is still there and you call it by name, v.Customer.Label(), which is exactly what the new method does to avoid repeating code.

Careful, this is not inheritance: a VIP is not a Customer and cannot be passed where one is expected; you pass v.Customer. And if two embedded types had a field with the same name, you would have to say which one, c.Contact.Name or c.Address.Name.

Code · main.go

package mainimport "fmt"type Contact struct{ Name, Email string }func (c Contact) Label() string { return c.Name + " <" + c.Email + ">" }type Address struct{ City, Country string }func (a Address) Line() string { return a.City + ", " + a.Country }// Customer embeds Contact and Address: their fields and methods are promoted.type Customer struct {	Contact	Address	Points int}type VIP struct { // embeds Customer and overrides its Label method	Customer	Tier string}func (v VIP) Label() string { return "[" + v.Tier + "] " + v.Customer.Label() }func main() {	c := Customer{Contact{"Ana Ruiz", "ana@shop.es"}, Address{"Seville", "Spain"}, 120}	fmt.Println(c.Name, "|", c.Label(), "|", c.Line(), "|", c.Points, "points")	v := VIP{Customer: c, Tier: "gold"}	v.Points += 50 // promoted through two levels: v.Customer.Points	fmt.Println(v.Label(), "|", v.Points, "points")	fmt.Println(v.Customer.Label(), "|", v.Contact.Email, "|", v.Line())}

Output · go run main.go

Ana Ruiz | Ana Ruiz <ana@shop.es> | Seville, Spain | 120 points
[gold] Ana Ruiz <ana@shop.es> | 170 points
Ana Ruiz <ana@shop.es> | ana@shop.es | Seville, Spain

Key points

  • Embedding means writing the type with no field name.
  • Fields and methods of the embedded type are promoted.
  • A method of your own with the same name shadows the promoted one.
  • The original is still reachable: v.Customer.Label().
  • It is composition, not inheritance: a VIP is not a Customer.

Exercise

Add a Postcode field to Address and make Line() include it. Then define a Labeler interface with Label() string and a function that takes a Labeler: pass it c and v and compare what it prints.