Passer au contenu principal
eLearner.app
Module 5 · Leçon 3 sur 523/50 dans le cours~12 min
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

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

Le 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 :

Go
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 :

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

Si 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) :

Go
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 :

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

L'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

Exercice#go.m5.l3.e1
Tentatives : 0Chargement…

Définissez Dog qui intègre Animal (intégration, pas de nom de champ) et possède un champ de chaîne Breed.

Chargement de l'éditeur…
Afficher l'indice

Embedding : écrivez le TYPE sans lui donner de nom de champ.

Solution disponible après 3 tentatives

Exercice#go.m5.l3.e2
Tentatives : 0Chargement…

Ajoutez une méthode chaîne Greet() à Animal et appelez-la directement sur une variable Dog (promotion de méthode).

Chargement de l'éditeur…
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

Quiz#go.m5.l3.e3
Prêt

Go prend-il en charge l’héritage classique ?

Go
type B struct { /* extends A? */ }
Options de réponse

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.