4 pinceaux de couleur

Le modèle de conception “Decorateur”

Le modèle de conception “Decorateur” est un modèle de conception structurelle utilisé pour étendre la fonctionnalité des objets d’une manière flexible et réutilisable.

En C#, l’utilisation du modèle de conception Decorator implique la création d’un ensemble de classes de décorateurs qui sont utilisées pour envelopper des composants concrets. Voici un exemple concret et quelques cas d’utilisation courants.

Créons un exemple simple impliquant un système Notifier où nous pouvons envoyer des notifications via différents canaux (Email, SMS, etc.).

Étape 1 : Créer une interface de composant

Cette étape est essentielle pour définir le contrat que les décorateurs devront mettre en œuvre.

1
2
3
4
public interface INotifier
{
    void Send(string message);
}

Étape 2 : Créer un composant concret

Ce composant implémente l’interface que nous avons définie précédemment.

1
2
3
4
5
6
7
public class EmailNotifier : INotifier
{
    public void Send(string message)
    {
        Console.WriteLine($"Sending Email: {message}");
    }
}

Étape 3 : Création de la classe décoratrice de base

La classe décoratrice de base nous sert de base pour implémenter le modèle de conception.

En utilisant l’injection de dépendance avec une instance de INotifier fournit au constructeur, on déclare une méthode virtuelle que chaque classe décoratrice concrète viendra surchargé.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class NotifierDecorator : INotifier
{
    protected INotifier _notifier;

    public NotifierDecorator(INotifier notifier)
    {
        _notifier = notifier;
    }

    public virtual void Send(string message)
    {
        _notifier.Send(message);
    }
}

Étape 4 : Créer des décorateurs concrets

Dans les décorateurs concrets, on hérite de NotifierDecorator et on l’a surchargé la méthode Send tout en s’assurant que la méthode de la classe parent est bien appelé.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class SMSNotifier : NotifierDecorator
{
    public SMSNotifier(INotifier notifier) : base(notifier)
    {
    }

    public override void Send(string message)
    {
        base.Send(message);
        Console.WriteLine($"And sending SMS: {message}");
    }
}

public class FacebookNotifier : NotifierDecorator
{
    public FacebookNotifier(INotifier notifier) : base(notifier)
    {
    }

    public override void Send(string message)
    {
        base.Send(message);
        Console.WriteLine($"And posting on Facebook: {message}");
    }
}

Du coup, à quoi ressemble l’utilisation des classes Notifier ? Quel résultat obtient-on ?

Étape 5: Utiliser les décorateurs

Mettons le code à l’épreuve :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine($"## Use case: just Email");
        INotifier emailNotifier = new EmailNotifier();
        emailNotifier.Send("Hello World!");

        Console.WriteLine($"## Use case: Email+SMS");
        var smsNotifier = new SMSNotifier(emailNotifier);
        smsNotifier.Send("Hello World!");

        Console.WriteLine($"## Use case: Email+SMS+Facebook");
        var facebookNotifier = new FacebookNotifier(smsNotifier);
        facebookNotifier.Send("Hello World!");

        Console.WriteLine($"## Use case: Email+Facebook");
        var facebookNotifier2 = new FacebookNotifier(emailNotifier);
        facebookNotifier2.Send("Hello World!");
    }
}

Quel est le résultat ?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
## Use case: just Email
Sending Email: Hello World!
## Use case: Email+SMS
Sending Email: Hello World!
And sending SMS: Hello World!
## Use case: Email+SMS+Facebook
Sending Email: Hello World!
And sending SMS: Hello World!
And posting on Facebook: Hello World!
## Use case: Email+Facebook
Sending Email: Hello World!
And posting on Facebook: Hello World!

Explication étape par étape

Le premier test est simple. Il n’y a rien de particulier à expliquer.

Les deuxième, troisième et quatrième tests démontrent le modèle de conception Decorator en action.

Si vous regardez le troisième test, facebookNotifier enveloppe smsNotifier, qui lui-même enveloppe emailNotifier.

emailNotifier est la base et quand vous appelez facebookNotifier.Send(« Hello World ! »), voici ce qui se passe :

  • FacebookNotifier.Send est appelé et la première chose qui se passe dans la méthode est un appel à base.Send(message) (qui est smsNotifier.Send).

  • Ensuite, SMSNotifier.Send est appelé. Encore une fois, la première chose qui se passe dans la méthode est un appel à base.Send(message) (qui est emailNotifier.Send).

  • Donc EmailNotifier.Send est appelé et affiche « Sending Email : Hello World ! » dans la console de sortie.

  • Ensuite, le contrôle retourne à SMSNotifier.Send, qui affiche « And sending SMS : Hello World ! » dans la console de sortie.

  • Enfin, le contrôle retourne à FacebookNotifier.Send, qui affiche « And posting on Facebook : Hello World ! » dans la console de sortie.

Chaque décorateur ajoute son propre comportement après avoir appelé la méthode de l’objet enveloppé.

1
2
3
4
5
6
FacebookNotifier.Send
    -> SMSNotifier.Send
        -> EmailNotifier.Send
            (prints "Sending Email: Hello World!")
        (prints "And sending SMS: Hello World!")
    (prints "And posting on Facebook: Hello World!")

Cas d’utilisation courants

Lorsque vous voulez réaliser l’un des cas d’utilisation ci-dessous, alors le modèle de conception Decorator s’avère approprié :

  1. Étendre la fonctionnalité lorsque vous souhaitez ajouter des responsabilités à des objets individuels, et non à une classe entière.
  2. Combiner des comportements lorsque vous devez ajouter une combinaison de comportements au moment de l’exécution. Par exemple, combiner plusieurs comportements de journalisation, d’authentification ou de notification.
  3. Adhérer au principe de responsabilité unique en utilisant des décorateurs, vous pouvez diviser la fonctionnalité d’une classe en classes distinctes ayant des responsabilités spécifiques.
  4. Construire une interface utilisateur pour envelopper les éléments de l’interface utilisateur pour ajouter des responsabilités telles que des bordures, des barres de défilement ou des décorations.

Ressources pour approfondir le sujet

Les ressources ci-dessous permettent d’approfondir le sujet et ses applications dans la conception de logiciels.

  • Design Patterns : Elements of Reusable Object-Oriented Software par Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (Gang of Four)
  • Head First Design Patterns par Eric Freeman, Elisabeth Robson
  • Microsoft Documentation on the Decorator Pattern
  • Why Adding Features Shouldn’t Break Your Code: Meet the Decorator Pattern par Maxim Gorin explique très bien le concept de décorateur que j’ai partagé ici. Je vous invite à sa série sur les modèles de conception qui est très complète.

Suivez-moi !

Merci d’avoir lu cet article. Assurez-vous de me suivre sur X, de vous abonner à ma publication Substack et d’ajouter mon blog à vos favoris pour ne pas manquer les prochains articles.

Photo de Nataliya Vaitkevich