Decorator Pattern : le modèle pour les extensions de classe dynamiques

Vous souhaitez étendre une classe existante dans un logiciel orienté objet avec de nouvelles fonctionnalités, vous avez deux options différentes :la solution simple, mais aussi rapidement déroutante, qui consiste à mettre en place des sous-classes complétant la classe de base de manière appropriée ou bien, autre solution, vous pouvez également utiliser une instance de décorateur selon le patron Décorateur. Ce modèle, qui est l’un des 23 Design Patterns GoF, permet une extension dynamique des classes pendant que le logiciel fonctionne. Cela élimine le besoin de hiérarchies d’héritage infiniment longues et difficiles à comprendre.

Ci-dessous, vous apprendrez ce qu’est exactement le Decorator Pattern et quels sont ses avantages et ses inconvénients. En outre, nous illustrerons le fonctionnement du modèle au moyen d’une représentation graphique et d’un exemple concret.

Qu’est-ce que le Decorator Pattern (patron Décorateur) ?

Le patron Decorator ou Decorator Pattern en anglais, est un patron de stratégie publié en 1994 pour l’extension claire des classes dans les logiciels informatiques orientés objet. Selon le patron, tout objet peut être étendu par un comportement souhaité sans affecter le comportement des autres objets de la même classe. Structurellement, le patron Décorateur est très similaire au patron de conception « Chaîne de responsabilité » mais contrairement à ce concept de responsabilité avec un agent central, les demandes sont acceptées par toutes les classes.

Le composant logiciel à étendre est « décoré » selon le patron de conception Décorateur avec une ou plusieurs classes de Decorator qui entourent complètement le composant. Chaque Décorateur est du même type que le composant joint et a donc la même interface. Cela lui permet de déléguer facilement les appels de méthode entrants au composant lié, tout en exécutant éventuellement son propre comportement avant ou après. Il est également possible de traiter un appel directement chez le Décorateur.

À quoi sert le patron Décorateur ?

Comme d’autres modèles GoF, tels que le Strategy Pattern ou le Builder Pattern, le patron Decorator vise à rendre les composants des logiciels orientés objet plus flexibles et plus faciles à réutiliser. À cette fin, l’approche fournit la solution pour ajouter ou supprimer des dépendances à un objet de manière dynamique et - si nécessaire - pendant l’exécution. Pour cette raison en particulier, le modèle est une bonne alternative à l’utilisation de sous-classes : elles peuvent également compléter une classe de différentes manières, mais ne permettent pas d’effectuer des ajustements pendant l’exécution.

Note

Un composant logiciel peut être étendu par un nombre quelconque de classes Decorator. Pour l’accès aux instances, ces extensions restent totalement invisibles, de sorte qu’elles ne remarquent même pas que des classes supplémentaires précèdent la classe réelle.

Decorator Pattern : diagramme UML pour illustration

Le Décorateur ou plutôt les classes Décorateur (DécorateurConcret) ont la même interface que le composant logiciel à décorer (ComposantConcret) et sont du même type. Ceci est important pour le traitement des appels, qui sont transmis soit inchangés, soit modifiés si le Décorateur ne s’occupe pas lui-même du traitement. Dans le concept de motif Décorateur, cette interface élémentaire, qui est essentiellement une superclasse abstraite, est appelée « composant ».

L’interaction entre le composant de base et le décorateur peut être illustrée au mieux par une représentation graphique des relations sous la forme d’un diagramme de classes UML. Dans l’illustration abstraite suivante du Decorator Pattern, nous avons donc utilisé le langage de modélisation pour la programmation orientée objet.

Les avantages et les inconvénients du Decorator Pattern en un coup d’œil

La prise en compte du patron Décorateur lors de la conception d’un logiciel est payante pour plusieurs raisons. Tout d’abord, il y a le haut degré de flexibilité qui vient avec une telle structure de décorateur : tant au moment de la compilation qu’à l’exécution, les classes peuvent être étendues avec de nouveaux comportements sans héritage. Cette approche de programmation n’entraîne pas de hiérarchies d’héritage floues, ce qui améliore également la lisibilité du code du programme.

Le fait que la fonctionnalité soit répartie entre plusieurs classes de décorateurs augmente également la performance du logiciel. Ainsi, vous pouvez appeler et lancer les fonctions dont vous avez besoin pour le moment. Avec une classe de base complexe, qui fournit toutes les fonctions en permanence, cette option optimisée en termes de ressources n’est pas disponible.

Cependant, le développement selon le patron Decorator n’a pas que des avantages : avec l’introduction du modèle, la complexité du logiciel augmente automatiquement. L’interface de Decorator en particulier est généralement très verbeuse et associée à de nombreux nouveaux termes, et donc tout sauf facile d’accès pour les débutants. Un autre inconvénient est le grand nombre d’objets Decorator, pour lesquels une systématisation séparée est recommandée afin d’éviter d’être confronté à des problèmes de vue d’ensemble, similaires à ceux rencontrés lors du travail avec des sous-classes. Les chaînes d’appel souvent très longues des objets décorés (c’est-à-dire les composants logiciels étendus) rendent également plus difficile la recherche d’erreurs et donc le processus de débogage en général.

Avantages Inconvénients
Un degré élevé de flexibilité Grande complexité du logiciel (en particulier l’interface Decorator)
Extension des fonctions des classes sans héritage Ne convient pas aux débutants
Un code de programme bien lisible Nombre élevé d’objets
Appels de fonction optimisés pour les ressources Processus de débogage difficile

Patron Décorateur : scénarios d’application typiques

Le Decorator Pattern constitue la base des objets dynamiques et transparents d’un logiciel. Les composants d’interfaces utilisateur graphiques sont un domaine d’application typique du patron : si, par exemple, un champ de texte doit être pourvu d’une bordure, un décorateur correspondant qui est « invisiblement » commuté entre l’objet champ de texte et l’appel est suffisant pour insérer ce nouvel élément d’interface.

Un exemple très connu de la mise en œuvre du patron de conception Décorateur est celui des classes de flux de la bibliothèque Java, qui sont responsables du traitement des données d’entrée et de sortie. Les classes de décorateurs sont utilisées ici notamment pour ajouter de nouvelles propriétés et informations de statut au flux de données ou pour fournir de nouvelles interfaces.

Bien entendu, Java n’est pas le seul langage de programmation dans lequel l’utilisation du patron Décorateur est courante. Les langages suivants s’appuient également sur le patron Decorator :

  • C++
  • C#
  • Go
  • JavaScript
  • Python
  • PHP

Exemple pratique pour la mise en œuvre du Decorator Pattern

La liste des avantages et des inconvénients montre que le patron Décorateur ne convient pas à tous les types de logiciels. Cependant, lorsqu’une classe doit être changée par la suite, et surtout dans les projets où cela ne peut se faire en utilisant des sous-classes, le modèle de conception est une solution de premier ordre.

Le point de départ dans ce cas est un logiciel qui rend les noms des personnes accessibles via la classe abstraite « employé ». Cependant, la première lettre des noms retrouvés est toujours en minuscule. Comme une adaptation ultérieure est impossible, la classe de décorateur « EmployéDecorator » est implémentée, qui fonctionne via la même interface et permet également l’appel de la méthode getName(). En outre, le décorateur reçoit une logique qui garantit que la première lettre est correctement mise en majuscule. L’exemple de code approprié ressemble à ceci :

public class EmployéDecorator implements Person {
private Employé employé;
public EmployéDecorator(Employé employé){
	this.employé = employé;
}
public String getName(){
	// appelle la méthode de la classe d’employés
	String name = employé.getName();
	// Veillez à ce que la première lettre soit en majuscule ici
	name = Character.toUpperCase(name.charAt(0)) 
	+ name.substring(1, name.length());
	return name;
}
}