Composite pattern : solution de modèle pour les hiérarchies partielles et entières

Les structures de données dynamiques telles que l’arborescence d’une gestion de fichiers ou d’une interface de programme nécessitent une structure hiérarchique claire et sans ambiguïté. Cependant, la mise en place de ce type de structure n’est souvent pas si facile. Par exemple, il est important de s’assurer que le type d’un objet ne doit pas être interrogé à chaque fois avant le traitement effectif des données, car ce scénario ne serait ni efficace ni performant. En particulier lorsque de nombreux objets primitifs rencontrent des objets composites, il est recommandé d’utiliser un patron composite. L’approche de conception de logiciels permet aux clients de traiter les objets individuels et composites de manière uniforme en cachant leurs différences au client.

Qu’est-ce qu’un composite pattern ?

Le patron composite est l’un des 23 patrons de conceptions pour le développement de logiciels publiés en 1994 par Erich Gamma, Richard Helm, Ralph Johnson et John Vlissides (connus sous le nom de GOF pour « Gang of Four »). À l’instar du patron de conception façade et du décorateur, c’est l’un des patrons de conception dont la fonction de base est de combiner des objets et des classes en structures plus grandes.

Le composite pattern suit l’idée de base de représenter des objets simples ainsi que leurs récipients ou compositions (c’est-à-dire des compositions d’objets) dans une classe abstraite afin de pouvoir les traiter de manière uniforme. Une telle structure est également appelée hiérarchie partielle et globale (en anglais part-whole hierarchy), dans laquelle un objet n’est toujours qu’une partie d’un tout ou le tout (qui est constitué de parties individuelles).

Pour quels problèmes le composite pattern est-il une solution ?

L’objectif fondamental du composite pattern est, comme pour tous les modèles GOF, de traiter au mieux les problèmes de conception récurrents dans le développement orienté objet. Le résultat souhaité est un logiciel aussi souple que possible et caractérisé par des objets faciles à mettre en œuvre, testables, échangeables et réutilisables. À cette fin, le patron composite décrit une manière de traiter de la même manière les objets simples et les objets composites. Il est ainsi possible de créer une structure d’objet facile à comprendre et permettant un accès au client le plus efficace possible. Il minimise également la tendance à l’erreur du code.

Composite pattern : représentation graphique (UML)

Afin de mettre en œuvre les hiérarchies complètes partielles efficaces mentionnées ci-dessus, le patron composite prévoit la mise en œuvre d’une interface de composants uniforme pour les objets partiels simples, également appelés leaf objects, et les objets composites. Les objets feuilles individuels intègrent directement cette interface, les objets composites transmettent automatiquement les demandes concrètes des clients pour l’interface à leurs composants subordonnés. Pour le client, peu importe le type d’objet (partie ou ensemble), puisqu’il n’a qu’à s’adresser à l’interface.

Le diagramme de classes qui suit, en langage de modélisation UML, explicite les connexions et les hiérarchies dans un logiciel basé sur un composite pattern.

Avantages et inconvénients du composite pattern

Le Composite Pattern est une constante dans le développement des logiciels. Ce sont surtout les projets dont les structures sont fortement imbriquées qui bénéficient de l’approche pratique de l’organisation des objets : qu’il s’agisse d’un objet primitif ou composite, avec des dépendances simples ou complexes : la profondeur et la largeur de l’imbrication n’ont en effet pas d’importance pour le modèle de conception composite. Les différences entre les types d’objets peuvent être complètement ignorées par le client, de sorte qu’aucune fonction distincte n’est requise pour l’accès. Cela présente l’avantage que le code client reste simple et léger.

Un autre point fort du composite pattern design est la flexibilité et l’extensibilité facile que le modèle donne à un logiciel : l’interface universelle des composants permet d’inclure de nouvelles feuilles et de nouveaux objets composites sans modification du code - que ce soit du côté du client ou dans des structures d’objets existantes.

Bien que le modèle composite et son interface uniforme offrent un certain nombre d’avantages, cette solution n’est pas exempte d’inconvénients. L’interface, en particulier, peut donner beaucoup de maux de tête aux développeurs. La mise en œuvre elle-même pose de grands défis, car il faut par exemple décider quelles opérations doivent être définies spécifiquement dans l’interface et lesquelles dans les classes composites. De plus, un ajustement ultérieur des propriétés du composite (par exemple, la restriction des éléments enfants autorisés) s’avère généralement compliqué et difficile à mettre en œuvre.

Avantages Inconvénients
Affiche les structures d’objets fortement imbriquées La mise en œuvre de l’interface des composants est très difficile
Code de programme simple et facile à comprendre Les ajustements ultérieurs des propriétés du composite sont compliqués et difficiles à mettre en œuvre.
Bonne capacité d’expansion  

Scénarios d’application du modèle composite

L’utilisation du modèle composite est rentable partout où des opérations doivent être effectuées sur des structures de données dynamiques dont la hiérarchie est d’une largeur et/ou d’une profondeur complexes. Dans ce cas, on parle également d’une structure arborescente binaire, qui est intéressante pour une grande variété de scénarios logiciels et qui est fréquemment utilisée. Voici ci-dessous des exemples typiques.

Systèmes de fichiers : les composants les plus importants des logiciels des appareils comprennent des systèmes de fichiers. Ces derniers peuvent être cartographiés de manière optimale avec le modèle composite : les fichiers individuels en tant qu’objets feuilles et les dossiers, qui peuvent eux-mêmes contenir des fichiers ou des dossiers supplémentaires, en tant qu’objets composites.

Menus du logiciel : les menus de programme représentent également un cas d’utilisation typique pour une structure arborescente binaire selon le composite pattern design. La barre de menu contient une ou plusieurs entrées racine (objets composites) comme « Fichier ». Ceux-ci donnent accès à divers éléments de menu, qui sont soit directement cliquables (Leaf), soit contiennent des menus subdivisés (composite).

Interfaces utilisateur graphiques (GUI) : les structures arborescentes et le modèle composite peuvent également jouer un rôle important dans la conception des interfaces utilisateur graphiques. Loin des simples éléments de feuille comme les boutons, les champs de texte ou les cases à cocher, le résumé des conteneurs composites comme les cadres ou les panneaux offre une structure claire et plus de clarté.

Exemple de code : modèle composite

Dans pratiquement aucun autre langage de programmation, l’approche par modèle composite n’est aussi fermement établie qu’en Java. Entre autres choses, le modèle est également à la base de l’Abstract Window Toolkit (AWT), une API pratique et populaire comprenant environ 50 classes Java prêtes à l’emploi pour le développement d’interfaces utilisateur Java multiplateformes. Dans l’exemple de code suivant, basé sur un modèle composite sur Java, sur baeldung.com, nous avons donc décidé d’utiliser ce langage de programmation populaire.

Dans cet exemple est présentée la structure hiérarchique des services (departments) d’une entreprise (company). Tout d’abord, l’interface du composant Department est définie ainsi :

public interface Department {
	void printDepartmentName();
}

Ensuite, les deux classes de feuilles simples FinancialDepartment (pour le département financier) et SalesDepartment (pour le département commercial) sont définies. Les deux implémentent la méthode printDepartmentName() de l’interface du composant, mais ne contiennent pas d’autres objets de département.

public class FinancialDepartment implements Department {
	private Integer id;
	private String name;
	public void printDepartmentName() {
		System.out.println(getClass().getSimpleName());
	}
	// standard constructor, getters, setters
}
public class SalesDepartment implements Department {
	private Integer id;
	private String name;
	public void printDepartmentName() {
		System.out.println(getClass().getSimpleName());
	}
	// standard constructor, getters, setters
}

Enfin, une classe composite correspondant à la hiérarchie est définie avec HeadDepartment. Cette classe se compose de plusieurs éléments de département et contient des méthodes pour ajouter des éléments supplémentaires (addDepartment) ou supprimer des éléments existants (removeDepartment) en plus de la méthode printDepartmentName :

public class HeadDepartment implements Department {
	private Integer id;
	private String name;
	private List<department> childDepartments;</department>
	public HeadDepartment(Integer id, String name) {
		this.id = id;
		this.name = name;
		this.childDepartments = new ArrayList<>();
	}
	public void printDepartmentName() {
		childDepartments.forEach(Department::printDepartmentName);
	}
	public void addDepartment(Department department) {
		childDepartments.add(department);
	}
	public void removeDepartment(Department department) {
		childDepartments.remove(department);
	}
}