Composition en Go

Qu'est-ce que la Composition ?

Dans le langage Go, la composition décrit la manière de structurer les données et les comportements en unissant plusieurs types plus petits au sein d'un type plus vaste. La composition est réalisée par l'embedding, une fonctionnalité qui permet à une structure d'hériter des champs et des méthodes d'une autre structure, facilitant ainsi la réutilisation du code et une conception modulaire. Grâce à la composition, il est possible de construire des types complexes à partir de composants plus simples, ce qui favorise la séparation des préoccupations et rend le code plus lisible et testable.

Créer des Structs

Prenons un exemple où nous définissons une structure appelée Pizza :

package main
import "fmt"
type Pizza struct {
  Name        string
  Size        string
  Toppings    []string
  IsDelicious bool
}

Cette structure peut être utilisée pour créer une fonction qui définit le style de la pizza commandée. Voici comment cela se présente :

package main
import "fmt"
type Pizza struct {
  Name        string
  Size        string
  Toppings    []string
  IsDelicious bool
}

func pizzaStyle(p Pizza) string {
  return p.Name + " pizza est une " + p.Size + " pizza avec les garnitures : " + fmt.Sprint(p.Toppings)
}

func main() {
  myPizza := Pizza{
    Name:        "Margherita",
    Size:        "moyenne",
    Toppings:    []string{"tomates", "mozzarella", "basilic"},
    IsDelicious: true,
  }
  fmt.Println(pizzaStyle(myPizza))
}

Ce code produit la sortie suivante :

Margherita pizza est une moyenne pizza avec les garnitures : [tomates mozzarella basilic]

Une Structure dans une Structure

Considérons maintenant un exemple où une structure contient une autre structure.

package main
import "fmt"
type Pizza struct {
  Name        string
  Size        string
  Toppings    []string
  IsDelicious bool
}

func pizzaStyle(p Pizza) string {
  return p.Name + " pizza est une " + p.Size + " pizza avec les garnitures : " + fmt.Sprint(p.Toppings)
}

type Restaurant struct {
  Name      string
  Rating    int
  PizzaMenu []Pizza
}

func restaurantInfo(r Restaurant) string {
  return r.Name + " a une note de " + fmt.Sprint(r.Rating) + " et propose les pizzas suivantes : " + fmt.Sprint(r.PizzaMenu)
}

func main() {
  myPizza := Pizza{
    Name:        "Margherita",
    Size:        "moyenne",
    Toppings:    []string{"tomates", "mozzarella", "basilic"},
    IsDelicious: true,
  }
  myRestaurant := Restaurant{
    Name:      "Pizzeria del Corso",
    Rating:    4,
    PizzaMenu: []Pizza{myPizza},
  }
  fmt.Println(pizzaStyle(myPizza))
  fmt.Println(restaurantInfo(myRestaurant))
}

Cet exemple fait apparaître que la structure Restaurant inclut la structure Pizza. La fonction restaurantInfo() prend une instance de Restaurant et retourne des informations sur le restaurant. La sortie quadruple est :

Margherita pizza est une moyenne pizza avec les garnitures : [tomates mozzarella basilic]
Pizzeria del Corso a une note de 4 et propose les pizzas suivantes : [{Margherita moyenne [tomates mozzarella basilic] true}]

Héritage par Composition

Dans Go, une structure peut hériter des champs et méthodes d'une structure incorporée grâce à la composition. L'héritage se fait avec la syntaxe suivante :

type Embedded struct {
  field string
}

func (a Embedded) printField() string {
  return a.field
}

type MainStruct struct {
  Embedded
}

func PrintEmbedded(m MainStruct) string {
  return m.printField()
}

Ajoutons maintenant un nouvel élément à notre exemple de pizza :

type Address struct {
  Street  string
  City    string
  Country string
}

func (a Address) printAddress() string {
  return a.Street + ", " + a.City
}

type Restaurant struct {
  Name      string
  Rating    int
  PizzaMenu []Pizza
  Address
}

func restaurantInfo(r Restaurant) string {
  return r.Name + ", situé à " + r.printAddress() + ", " + r.Country + " a une note de " + fmt.Sprint(r.Rating) + " et sert les pizzas suivantes : " + fmt.Sprint(r.PizzaMenu)
}

func main() {
  myPizza := Pizza{
    Name:        "Margherita",
    Size:        "moyenne",
    Toppings:    []string{"tomates", "mozzarella", "basilic"},
    IsDelicious: true,
  }
  myAddress := Address{
    Street: "1st Avenue",
    City:   "New York",
    Country: "USA",
  }
  myRestaurant := Restaurant{
    Name:      "Pizzeria del Corso",
    Rating:    4,
    PizzaMenu: []Pizza{myPizza},
    Address:   myAddress,
  }
  fmt.Println(pizzaStyle(myPizza))
  fmt.Println(restaurantInfo(myRestaurant))
}

Ce code produira le résultat suivant :

Margherita pizza est une moyenne pizza avec les garnitures : [tomates mozzarella basilic]
Pizzeria del Corso, situé à 1st Avenue, New York, USA a une note de 4 et sert les pizzas suivantes : [{Margherita moyenne [tomates mozzarella basilic] true}]

Les Avantages de la Composition

La composition est une technique puissante pour la création de structures et d'objets complexes. Elle permet de décomposer des problèmes en éléments plus simples, ensuite gérés de manière structurée. Dans le design logiciel, il est essentiel de réfléchir sur la composition des composants créés et sur leur intégration au sein d'un système plus large. En adoptant une approche compositionnelle, le logiciel développé est flexible, évolutif et facilement maintenable.