Direct naar de hoofdinhoud
Module 5 · Les 3 van 523/50 in de cursus~12 min
Modulelessen (3/5)

Samenstelling (inbedding)

Go heeft geen erfenis. Het idiomatische antwoord op hergebruik is: compositie, en vooral inbedding: inclusief één type in een andere zonder er een veldnaam aan te geven. De velden en methoden van het ingebedde type wordt gepromoot en wordt direct toegankelijk alsof ze behoorden tot het uiterlijke type.

Structurele inbedding

Go
type Animal struct {
    Name string
}

func (a Animal) Greet() string {
    return "ciao da " + a.Name
}

type Dog struct {
    Animal        // embedding — NO field name
    Breed string
}

c := Dog{
    Animal: Animal{Name: "Rex"},
    Breed:  "Pastore",
}

fmt.Println(c.Name)         // "Rex" — promoted from Animal
fmt.Println(c.Greet())      // "ciao da Rex" — promoted method
fmt.Println(c.Breed)        // own field

Het ingesloten veld is nog steeds toegankelijk via de naam van het type: c.Animal.Name werkt en is identiek aan c.Name. Het is nodig wanneer er zijn botsingen.

Overschrijven (gedeeltelijk): methode "schaduwen"

Als het buitenste type een methode definieert met dezelfde naam als het gepromote één, de methode "wint". Van binnenuit kun je altijd de ingebedde bereiken één via de typenaam:

Go
func (c Dog) Greet() string {
    return "BAU! " + c.Animal.Greet()
}

Het is geen polymorfe overerving: het is slechts lexicale naamomzetting. Voor polymorfisme gebruik je interfaces (Module 6).

Meerdere inbedding

U kunt meerdere typen insluiten:

Go
type Logger struct{ /*...*/ }
func (l Logger) Log(msg string) { /*...*/ }

type Timer struct{ /*...*/ }
func (t Timer) Now() time.Time { /*...*/ }

type Service struct {
    Logger
    Timer
    Name string
}

s := Service{Name: "api"}
s.Log("start")   // promoted from Logger
s.Now()          // promoted from Timer

Als twee ingebedde typen een methode hebben met DEZELFDE naam, directe toegang wordt dubbelzinnig en de compiler vereist de expliciete vorm (s.Logger.Foo() of s.Timer.Foo()).

Interface-insluiting

Interfaces kunnen ook worden "ingebed" (zie Module 6):

Go
type ReadWriter interface {
    io.Reader
    io.Writer
}

Het is equivalent aan de combinatie van de methoden van Reader en Writer.

Wanneer gebruik je expliciete compositie (niet inbedden)

Als u een benoemd veld en geen automatische promotie wilt, gebruikt u gewoon samenstelling:

Go
type Dog struct {
    Anim  Animal    // NORMAL field, no promotion
    Breed string
}
c.Anim.Name   // you need the field name

Insluiten is een handig delegatiehulpmiddel, geen ontwerpkeuze strikt beter dan expliciete compositie: gebruik het wanneer je echt wilt om de API van het innerlijke type te promoten.

Probeer het

Oefening#go.m5.l3.e1
Pogingen: 0Laden…

Definieer Hond die Dier insluit (inbedding, geen veldnaam) en een tekenreeksveld Ras heeft.

Editor laden…
Toon hint

Insluiten: schrijf het TYPE zonder het een veldnaam te geven.

Oplossing beschikbaar na 3 pogingen

Oefening#go.m5.l3.e2
Pogingen: 0Laden…

Voeg een Greet()-reeksmethode toe aan Animal en roep deze rechtstreeks aan op een Dog-variabele (methodepromotie).

Editor laden…
Toon hint

De methode die op Animal is gedefinieerd, wordt dankzij de inbedding gepromoveerd naar Dog.

Oplossing beschikbaar na 3 pogingen

Quiz#go.m5.l3.e3
Klaar

Ondersteunt Go klassieke overerving?

Go
type B struct { /* extends A? */ }
Antwoordmogelijkheden

Samenvatten

  • Geen erfenis: u schrijft met embedding.
  • Embedden = het TYPE schrijven zonder veldnaam in de struct.
  • Velden en methoden van het ingebedde type worden gepromoot.
  • Expliciete toegang is altijd beschikbaar: outer.Inner.Field.
  • "Lexicale" overschrijving: de methode van het buitenste type overschaduwt de gepromote methode.
  • Inbedding ≠ is-a: gebruik voor polymorfisme interfaces.