DMarlon Studio

Lección 3 · 3 de 20

Incrustación y composición · Go

Componga tipos en Go incrustando structs: campos y métodos promovidos, cómo sustituir un método y por qué no es herencia. Clientes y direcciones.

Go · Incrustación y composición en DMarlon Studio: el código del ejemplo y su salida en la terminal

Go no tiene herencia; tiene composición. Customer incluye Contact y Address escribiendo solo el tipo, sin nombre de campo: eso es un campo incrustado (*embedding*). El campo existe y se llama como su tipo, c.Contact y c.Address.

Los campos y métodos del tipo incrustado se promueven: c.Name es en realidad c.Contact.Name y c.City es c.Address.City. Lo mismo con los métodos: c.Label() y c.Line() funcionan como si Customer los hubiera declarado, y por tanto Customer cumple cualquier interfaz que pida Label() string.

La promoción atraviesa varios niveles. VIP incrusta Customer, así que v.Points += 50 modifica v.Customer.Points (de 120 a 170) y v.Contact.Email llega hasta el contacto, dos niveles más abajo.

VIP declara su propio Label, que tapa el promovido: v.Label() añade la categoría, [gold]. El original sigue ahí y se llama nombrándolo, v.Customer.Label(), que es lo que hace el nuevo método para no repetir código.

Ojo, no es herencia: un VIP no es un Customer y no se puede pasar donde se espera uno; hay que pasar v.Customer. Y si dos tipos incrustados tuvieran un campo con el mismo nombre, habría que indicar cuál con c.Contact.Name o c.Address.Name.

Código · 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())}

Salida · 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

Para recordar

  • Incrustar es escribir el tipo sin nombre de campo.
  • Los campos y métodos del tipo incrustado se promueven.
  • Un método propio con el mismo nombre tapa el promovido.
  • El original sigue accesible: v.Customer.Label().
  • Es composición, no herencia: un VIP no es un Customer.

Ejercicio

Añada a Address un campo Postcode y haga que Line() lo incluya. Después defina una interfaz Labeler con Label() string y una función que reciba un Labeler: pásele c y v y compare lo que escribe.