Fabrique
Intention
Fabrique est un patron de conception de création qui définit une interface pour créer des objets dans une classe mère, mais délègue le choix des types d’objets à créer aux sous-classes.
Problème
Imaginez que vous êtes en train de créer une application de gestion logistique. La première version de votre application ne propose que le transport par camion, la majeure partie de votre code est donc située dans la classe Camion
.
Au bout d’un certain temps, votre application devient populaire et de nombreuses entreprises de transport maritime vous demandent tous les jours d’ajouter la gestion de la logistique maritime dans l’application.
C’est super, n’est-ce pas ? Mais qu’en est-il du code ? La majeure partie est actuellement couplée à la classe Camion
. Pour pouvoir ajouter des Bateaux
dans l’application, il faudrait revoir la base du code. De plus, si vous décidez plus tard d’ajouter un autre type de transport dans l’application, il faudra effectuer à nouveau ces changements.
Par conséquent, vous allez vous retrouver avec du code pas très propre, rempli de conditions qui modifient le comportement du programme en fonction de la classe des objets de transport.
Solution
Le patron de conception fabrique vous propose de remplacer les appels directs au constructeur de l’objet (à l’aide de l’opérateur new
) en appelant une méthode fabrique spéciale. Pas d’inquiétude, les objets sont toujours créés avec l’opérateur new
, mais l’appel se fait à l’intérieur de la méthode fabrique. Les objets qu’elle retourne sont souvent appelés produits.
À première vue, cette modification peut sembler inutile : nous avons juste déplacé l’appel du constructeur dans une autre partie du programme. Mais maintenant, vous pouvez redéfinir la méthode fabrique dans la sous-classe et changer la classe des produits créés par la méthode.
Il y a tout de même une petite limitation : les sous-classes peuvent retourner des produits différents seulement si les produits ont une classe de base ou une interface commune. De plus, cette interface doit être le type retourné par la méthode fabrique de la classe de base.
Par exemple, les classes Camion
et Bateau
doivent toutes les deux implémenter l’interface Transport
, qui déclare une méthode livrer
. Chaque classe implémente cette méthode à sa façon : les camions livrent par la route et les bateaux livrent par la mer. La méthode fabrique de la classe LogistiqueRoute
retourne des camions, alors que celle de la classe LogistiqueMer
retourne des bateaux.
Le code qui appelle la méthode fabrique (souvent appelé le code client) ne fait pas la distinction entre les différents produits concrets retournés par les sous-classes, il les considère tous comme des Transports
abstraits. Le client sait que tous les objets transportés sont censés avoir une méthode livrer
, mais son fonctionnement lui importe peu.
Structure
-
L’interface est déclarée par le Produit et est commune à tous les objets qui peuvent être conçus par le créateur et ses sous-classes.
-
Les Produits Concrets sont différentes implémentations de l’interface produit.
-
La méthode fabrique est déclarée par la classe Créateur et retourne les nouveaux produits. Il est important que son type de retour concorde avec l’interface produit.
Vous pouvez rendre la méthode fabrique abstraite afin d’obliger ses sous-classes à implémenter leur propre version de la méthode ou vous pouvez modifier la méthode fabrique de la classe de base afin qu’elle retourne un type de produit par défaut.
Il faut bien comprendre que malgré son nom, la création de produits n’est pas la responsabilité principale du créateur. La classe créateur a en général déjà un fonctionnement propre lié à la nature de ses produits. La fabrique aide à découpler cette logique des produits concrets. C’est un peu comme une grande entreprise de développement de logiciels : elle peut posséder un département spécialisé dans la formation des développeurs, mais son activité principale reste d’écrire du code, pas de produire des développeurs.
-
Les Créateurs Concrets redéfinissent la méthode fabrique de la classe de base afin de pouvoir retourner les différents types de produits.
Notez toutefois que la méthode fabrique n’est pas obligée de créer tout le temps de nouvelles instances. Elle peut retourner des objets depuis un cache, un réservoir d’objets ou une autre source.
Pseudo-code
Cet exemple montre comment la fabrique peut être utilisée pour créer des éléments d’une UI (interface utilisateur) multiplateforme sans coupler le code client aux classes concrètes de l’UI.
La classe de base dialogue utilise différents éléments d’UI pour le rendu de ses fenêtres. Ces éléments peuvent un peu varier en fonction du système d’exploitation, mais ils doivent garder le même comportement. Un bouton sous Windows est aussi un bouton sous Linux.
Lorsque la fabrique entre dans l’équation, il est inutile de réécrire la logique de la boîte de dialogue pour chaque système d’exploitation. Si nous déclarons une méthode fabrique qui crée des boutons à l’intérieur de la classe de base dialogue, nous pourrons par la suite sous-classer cette dernière afin que la méthode fabrique retourne des boutons dotés du style Windows. La sous-classe hérite alors du code de la classe de base dialogue, mais grâce à la fabrique, elle est capable d’afficher à l’écran des boutons à l’allure Windows.
Pour le bon fonctionnement de ce patron, la classe de base dialogue doit utiliser des boutons abstraits représentés par une classe de base ou une interface dont tous les boutons concrets hériteront. Ainsi, quel que soit le type de boutons, le code de la classe dialogue reste fonctionnel.
Bien sûr, cette approche fonctionne également avec les autres éléments de l’UI. Cependant, chaque nouvelle méthode fabrique ajoutée à Dialogue nous rapproche du patron de conception Fabrique abstraite. Pas de panique, nous aborderons ce patron plus tard !
Possibilités d’application
Utilisez la fabrique si vous ne connaissez pas à l’avance les types et dépendances précis des objets que vous allez utiliser dans votre code.
La fabrique effectue une séparation entre le code du constructeur et le code qui utilise réellement le produit. Le code du constructeur devient ainsi plus évolutif et indépendant du reste du code.
Par exemple, si vous voulez ajouter un nouveau produit dans l’application, il vous suffit d’ajouter une sous-classe de création et d’y redéfinir la méthode fabrique.
Utilisez la fabrique si vous voulez mettre à disposition une librairie ou un framework pour vos utilisateurs avec un moyen d’étendre ses composants internes.
L’héritage est probablement le moyen le plus simple pour étendre le comportement par défaut d’une librairie ou d’un framework. Mais comment le framework peut-il savoir qu’il doit utiliser votre sous-classe plutôt qu’un composant standard ?
La solution est de réunir dans une seule méthode fabrique le code qui construit les composants dans le framework, et non seulement d’étendre ceux-ci, mais de laisser la possibilité de redéfinir la méthode fabrique.
Voyons un exemple d’utilisation. Imaginez la conception d’une application qui utilise un framework d’UI open source. Vous désirez utiliser des boutons ronds, mais le framework ne fournit que des boutons carrés. Vous étendez le Bouton
standard avec une magnifique sous-classe BoutonRond
. Mais vous devez à présent expliquer à la classe principale UIFramework
qu’elle doit utiliser la sous-classe du nouveau bouton plutôt que celle par défaut.
Pour ce faire, vous créez une sous-classe UIAvecBoutonsRonds
depuis une classe de base du framework et redéfinissez sa méthode créerBouton
. Même si la méthode de la classe de base retourne des Boutons
, votre sous-classe renvoie des BoutonsRonds
. Vous pouvez dorénavant utiliser UIAvecBoutonsRonds
à la place de UIFramework
. Et c’est à peu près tout !
Utilisez la fabrique lorsque vous voulez économiser des ressources système en réutilisant des objets au lieu d’en construire de nouveaux.
Le besoin se présente souvent lorsque l’on utilise des objets qui prennent beaucoup de ressources tels que des bases de données, des systèmes de fichiers ou des ressources réseau.
Que faut-il pour réutiliser un objet existant ?
- Tout d’abord, vous devez créer un moyen de stockage afin de garder la trace de tous les objets créés.
- Lorsqu’un nouvel objet est demandé, le programme doit chercher un objet libre dans cette réserve.
- … et le renvoyer au code client.
- Si aucun objet n’est disponible, le programme en crée un nouveau (et l’ajoute à la réserve).
Cela représente un paquet de code ! De plus, il faut tout mettre au même endroit afin de ne pas polluer le code avec des doublons.
Il serait probablement plus pratique de l’écrire dans le constructeur de la classe de l’objet que l’on veut réutiliser, mais par définition, un constructeur doit toujours renvoyer de nouveaux objets. Il ne peut pas retourner des instances existantes.
C’est pourquoi vous devez disposer d’une méthode non seulement capable de créer de nouveaux objets, mais aussi de réutiliser ceux qui existent déjà. Cela ressemble énormément à un patron de conception fabrique.
Mise en œuvre
-
Implémentez la même interface pour tous les produits. Cette interface doit déclarer des méthodes que tous les produits peuvent avoir en commun.
-
Ajoutez une méthode fabrique vide à l’intérieur de la classe créateur. Le type de retour de la méthode doit correspondre à l’interface commune des produits.
-
Localisez toutes les références aux constructeurs des produits dans le code du créateur. Remplacez-les une par une par des appels à la méthode fabrique et déplacez le code de la création de produits dans la méthode fabrique.
Vous allez peut-être devoir ajouter un paramètre temporaire à la méthode fabrique pour vérifier le type du produit retourné.
À ce stade, le code de la méthode fabrique peut paraître désordonné. Il pourrait même contenir un gros
switch
qui choisit la classe à instancier. Ne vous inquiétez pas, tout va bientôt rentrer dans l’ordre. -
Pour chaque type de produit listé dans la méthode fabrique, créez une sous-classe de Créateur. Redéfinissez la méthode fabrique dans les sous-classes et récupérez les morceaux de code appropriés de la méthode de base.
-
S’il y a trop de types de produits et peu d’intérêt de créer des sous-classes pour tous, vous pouvez réutiliser le paramètre de contrôle de la classe de base dans les sous-classes.
Imaginons la hiérarchie de classes suivante : la classe de base
Courrier
avec les sous-classesCourrierAérien
etCourrierTerrestre
; les classes deTransport
sontAvion
,Camion
etTrain
. La classeCourrierAérien
n’utilise que desAvions
et la classeCourrierTerrestre
peut utiliser à la fois desCamions
et desTrains
. Vous pouvez créer une nouvelle sous-classe (CourrierFerroviaire
par exemple) pour gérer les deux cas, mais il y a une autre possibilité. Le code client peut passer un argument à la méthode fabrique duCourrierTerrestre
pour désigner le type de produit qu’elle veut recevoir. -
Si après tous ces changements la méthode fabrique de base est devenue complètement vide, vous pouvez la rendre abstraite. S’il reste encore quelques lignes, vous pouvez y laisser un comportement par défaut.
Avantages et inconvénients
- Vous désolidarisez le Créateur des produits concrets.
- Principe de responsabilité unique. Vous pouvez déplacer tout le code de création des produits au même endroit, permettant ainsi une meilleure maintenabilité.
- Principe ouvert/fermé. Vous pouvez ajouter de nouveaux types de produits dans le programme sans endommager l’existant.
- Le code peut devenir plus complexe puisque vous devez introduire de nombreuses sous-classes pour la mise en place du patron. La condition optimale d’intégration du patron dans du code existant se présente lorsque vous avez déjà une hiérarchie existante de classes de création.
Liens avec les autres patrons
-
La Fabrique est souvent utilisée dès le début de la conception (moins compliquée et plus personnalisée grâce aux sous-classes) et évolue vers la Fabrique abstraite, le Prototype, ou le Monteur (ce dernier étant plus flexible, mais plus compliqué).
-
Les classes Fabrique abstraite sont souvent basées sur un ensemble de Fabriques, mais vous pouvez également utiliser le Prototype pour écrire leurs méthodes.
-
Vous pouvez utiliser la Fabrique avec l’Itérateur pour permettre aux sous-classes des collections de renvoyer différents types d’itérateurs compatibles avec les collections.
-
Le Prototype n’est pas basé sur l’héritage, il n’a donc pas ses désavantages. Mais le prototype requiert une initialisation compliquée pour l’objet cloné. La Fabrique est basée sur l’héritage, mais n’a pas besoin d’une étape d’initialisation.
-
La Fabrique est une spécialisation du Patron de méthode. Une fabrique peut aussi faire office d’étape dans un grand patron de méthode.
Informations supplémentaires
- Lisez notre Comparaison des fabriques si vous peinez à saisir la nuance entre les différents concepts et patrons.