Menu
Coddy logo textTech

Patron Décorateur

Fait partie de la section Programmation Orientée Objet du Journey Dart de Coddy — leçon 96 sur 110.

Le patron Décorateur vous permet d'ajouter dynamiquement de nouveaux comportements à des objets en les enveloppant dans des objets spéciaux appelés décorateurs. Contrairement à l'héritage, qui ajoute du comportement au moment de la compilation, les décorateurs attachent des responsabilités supplémentaires au moment de l'exécution sans modifier la classe d'origine.

L'idée clé est que l'objet original et les décorateurs implémentent tous deux la même interface.

Un décorateur enveloppe l'objet original, lui délègue des tâches et ajoute son propre comportement avant ou après :

abstract class Coffee {
  String getDescription();
  double getCost();
}

class SimpleCoffee implements Coffee {
  @override
  String getDescription() => 'Simple Coffee';

  @override
  double getCost() => 2.0;
}

class MilkDecorator implements Coffee {
  final Coffee _coffee;
  MilkDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Milk';

  @override
  double getCost() => _coffee.getCost() + 0.5;
}

class SugarDecorator implements Coffee {
  final Coffee _coffee;
  SugarDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Sugar';

  @override
  double getCost() => _coffee.getCost() + 0.2;
}

void main() {
  Coffee coffee = SimpleCoffee();
  coffee = MilkDecorator(coffee);
  coffee = SugarDecorator(coffee);

  print(coffee.getDescription());  // Simple Coffee, Milk, Sugar
  print(coffee.getCost());         // 2.7
}

Chaque décorateur enveloppe l'objet précédent et l'améliore. Vous pouvez empiler plusieurs décorateurs dans n'importe quelle combinaison, créant ainsi un comportement flexible sans avoir à créer une sous-classe pour chaque combinaison possible. Ce patron est idéal lorsque vous devez ajouter des fonctionnalités à des objets sans affecter les autres objets de la même classe.

challenge icon

Défi

Facile

Construisons un système de commande de pizza en utilisant le motif Decorator ! Vous allez créer une pizza de base qui peut être enrichie de diverses garnitures, où chaque garniture enveloppe l'élément précédent et s'ajoute à la fois à la description et au prix.

Vous organiserez votre code en deux fichiers :

  • pizza.dart : Ce fichier contient votre interface pizza et toutes les classes de décorateurs. Commencez par une classe abstraite Pizza qui définit deux méthodes : String getDescription() et double getPrice(). Ensuite, créez une classe PlainPizza qui implémente Pizza - elle retourne Plain Pizza comme description et coûte 8.0. Maintenant, construisez trois classes de décorateurs qui enveloppent chacune une Pizza et l'enrichissent :
    • CheeseDecorator - ajoute , Cheese à la description et 1.5 au prix
    • PepperoniDecorator - ajoute , Pepperoni à la description et 2.0 au prix
    • MushroomDecorator - ajoute , Mushrooms à la description et 1.25 au prix
    Chaque décorateur doit implémenter Pizza, accepter une Pizza dans son constructeur, et déléguer à la pizza enveloppée avant d'ajouter sa propre contribution.
  • main.dart : Importez votre fichier pizza et démontrez comment les décorateurs s'empilent pour construire une commande personnalisée. Commencez par une PlainPizza, puis enveloppez-la avec CheeseDecorator, puis enveloppez cela avec PepperoniDecorator, et enfin enveloppez le tout avec MushroomDecorator. Affichez la description finale de la pizza, puis affichez Total: $ suivi du prix.

Remarquez comment chaque décorateur s'appuie sur le précédent - vous créez une pizza toute garnie en empilant des décorateurs simples à responsabilité unique !

Sortie attendue :

Plain Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.75

Aide-mémoire

Le patron Décorateur (Decorator pattern) ajoute dynamiquement de nouveaux comportements aux objets en les enveloppant dans des objets décorateurs. L'objet original et les décorateurs implémentent tous deux la même interface.

Composants clés :

  • Une classe abstraite ou une interface qui définit les méthodes communes
  • Une classe concrète qui implémente la fonctionnalité de base
  • Des classes décoratrices qui enveloppent l'objet original et l'améliorent

Chaque décorateur :

  • Implémente la même interface que l'objet qu'il décore
  • Détient une référence à l'objet enveloppé
  • Délègue les appels à l'objet enveloppé
  • Ajoute son propre comportement avant ou après la délégation
abstract class Coffee {
  String getDescription();
  double getCost();
}

class SimpleCoffee implements Coffee {
  @override
  String getDescription() => 'Simple Coffee';

  @override
  double getCost() => 2.0;
}

class MilkDecorator implements Coffee {
  final Coffee _coffee;
  MilkDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Milk';

  @override
  double getCost() => _coffee.getCost() + 0.5;
}

// Stack multiple decorators
Coffee coffee = SimpleCoffee();
coffee = MilkDecorator(coffee);
coffee = SugarDecorator(coffee);

Les décorateurs peuvent être empilés dans n'importe quelle combinaison, créant un comportement flexible sans avoir à créer des sous-classes pour chaque combinaison possible.

Essayez vous-même

import 'pizza.dart';

void main() {
  // TODO: Créer une PlainPizza
  
  // TODO: L'envelopper avec CheeseDecorator
  
  // TODO: Envelopper cela avec PepperoniDecorator
  
  // TODO: Tout envelopper avec MushroomDecorator
  
  // TODO: Afficher la description finale de la pizza
  
  // TODO: Afficher "Total: $" suivi du prix
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation Orientée Objet