Leçons du module (3/5)
Composition (imbrication)
Allez n'a pas d'héritage. La réponse idiomatique à la réutilisation est composition, et en particulier embedding : incluant un type dans un autre sans lui donner de nom de champ. Les domaines et les méthodes de le type intégré est promu, devenant directement accessible comme si ils appartenaient au type extérieur.
Intégration de structure
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 fieldLe champ intégré est toujours accessible par le nom du type :
c.Animal.Name fonctionne et est identique à c.Name. Il est nécessaire quand
il y a des collisions.
Override (partiel) : méthode "shadowing"
Si le type externe définit une méthode portant le même nom que le type promu Premièrement, sa méthode « gagne ». De l'intérieur, vous pouvez toujours accéder à l'intégré un via le nom du type :
func (c Dog) Greet() string {
return "BAU! " + c.Animal.Greet()
}Il ne s’agit pas d’un héritage polymorphe : c’est juste d’une résolution de noms lexicaux. Pour le polymorphisme, vous utilisez des interfaces (Module 6).
Intégration multiple
Vous pouvez intégrer plusieurs types :
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 TimerSi deux types incorporés ont une méthode avec LE MÊME nom, accès direct
devient ambigu et le compilateur nécessite la forme explicite
(s.Logger.Foo() ou s.Timer.Foo()).
Intégration de l'interface
Les interfaces peuvent également être « embarquées » (voir Module 6) :
type ReadWriter interface {
io.Reader
io.Writer
}C'est l'équivalent de l'union des méthodes Reader et Writer.
Quand utiliser la composition explicite (sans intégration)
Si vous voulez un champ nommé et pas de promotion automatique, utilisez plain composition :
type Dog struct {
Anim Animal // NORMAL field, no promotion
Breed string
}
c.Anim.Name // you need the field nameL'intégration est un outil de délégation pratique, pas un choix de conception strictement mieux qu'une composition explicite : utilisez-la quand vous le souhaitez vraiment pour promouvoir l'API du type interne.
Essayez-le
Définissez Dog qui intègre Animal (intégration, pas de nom de champ) et possède un champ de chaîne Breed.
Afficher l'indice
Embedding : écrivez le TYPE sans lui donner de nom de champ.
Solution disponible après 3 tentatives
Ajoutez une méthode chaîne Greet() à Animal et appelez-la directement sur une variable Dog (promotion de méthode).
Afficher l'indice
La méthode définie sur Animal est promue en Dog grâce à l'intégration.
Solution disponible après 3 tentatives
Go prend-il en charge l’héritage classique ?
type B struct { /* extends A? */ }Récapitulatif
- Pas d'héritage : vous composez avec embedding.
- Embedding = écrire le TYPE sans nom de champ dans la structure.
- Les champs et méthodes de type intégré sont promus.
- L'accès explicite est toujours disponible :
outer.Inner.Field. - Override "Lexical" : la méthode du type externe masque celle promue.
- Embedding ≠ is-a : pour le polymorphisme, utilisez interfaces.