In de programmeertaal Go wordt ondersteuning voor compositie gerealiseerd via een mechanisme genaamd embedding — dit is de mogelijkheid om de ene structuur binnen de andere op te nemen zonder een expliciete veldnaam. Het idee was dat deze aanpak het mogelijk zou maken om 'op onze manier' over erven te modelleren, terwijl de eenvoud van de taal behouden blijft en veel complicaties gerelateerd aan meervoudig erven worden vermeden.
Een ontwikkelaar wil vaak het gedrag of de interface van een bepaalde basisstructuur uitbreiden zonder de architectuur te compliceren. In Go wordt hiervoor vaak embedding gebruikt, wat het mogelijk maakt om direct toegang te krijgen tot de methoden en velden van het geneste type, alsof ze in de bovenliggende structuur zijn gedefinieerd. Maar embedding heeft zijn eigen bijzonderheden, en een verkeerd begrip van het mechanisme kan leiden tot onvoorziene fouten, zoals naamconflicten, dubbele embedding en onjuist methoden erven.
Correct en spaarzaam gebruik van embedding maakt het mogelijk om een schonere compositie te bereiken. Het is belangrijk om te onthouden dat methoden en velden "maar één niveau" omhoog worden gehaald, en dat embedding precies een "has-a"-relatie implementeert, en niet een "is-a"-relatie. Men moet naamconflicten vermijden en begrijpen hoe de method receiver werkt.
Voorbeeldcode:
package main import "fmt" type Engine struct { Power int } func (e Engine) Start() { fmt.Println("Motor gestart met vermogen", e.Power) } type Car struct { Engine // embedding, geen Engine veld zoals engine Engine Brand string } func main() { c := Car{Engine: Engine{Power: 200}, Brand: "Toyota"} c.Start() // direct toegankelijk fmt.Println(c.Power) // veld is ook omhoog gehaald }
Belangrijkste kenmerken:
Kan embedding meervoudig erven implementeren?
Nee, embedding implementeert geen erven zoals in OOP; het is compositie. In één struct kunnen meerdere andere structuren worden embedded, maar bij overeenkomende methoden ontstaat er een compilatiefout, geen samenvoeging.
Wat gebeurt er als velden van ingesloten structuren dezelfde namen hebben?
Er zal een compilatiefout optreden bij directe toegang: de compiler weet niet naar welk veld te verwijzen. Men moet expliciet de weg via de naam van de ingesloten structuur opgeven.
Voorbeeldcode:
type A struct {X int} type B struct {X int} type C struct { A B } func main() { c := C{} // c.X = 1 // fout: ambiguë selector c.A.X = 1 // alleen zo }
Worden methoden met een waarde/aanwijzer gelijk behandeld bij embedding?
Nee. Methoden met een pointer receiver worden alleen omhoog gehaald als de structuur ook een pointer gebruikt (c := &Car{}). Als de structuur per waarde is, worden methoden met een pointer receiver niet "verhoogd".
Een project met diep geneste structuren, waar embedding wordt gebruikt om een meerlaagse erfenis te simulereren. Een nieuweling begrijpt niet uit welke structuur een veld of methode komt, het hele project wordt gecompliceerd en lijkt "magisch".
Voordelen:
Nadelen:
Embedding wordt gebruikt om een interface te implementeren, bijvoorbeeld, de Logger-interface wordt geïmplementeerd via embedding van een type met de Println-methode, en dit is duidelijk gedocumenteerd en gedekt met tests.
Voordelen:
Nadelen: