Prototype
Intention
Prototype est un patron de conception qui crée de nouveaux objets à partir d’objets existants sans rendre le code dépendant de leur classe.
Problème
Nous voulons une copie exacte d’un objet. Comment vous y prendriez-vous ? Tout d’abord, vous devez créer un nouvel objet de cette même classe. Ensuite, vous devez parcourir tous les attributs de l’objet d’origine et copier leurs valeurs dans le nouvel objet.
Super ! Mais il y a un hic. Certains objets ne peuvent pas être copiés ainsi, car leurs attributs peuvent être privés et ne pas être visibles en dehors de l’objet.
Un autre problème se profile avec l’approche directe. Étant donné que vous devez connaître la classe de l’objet pour le dupliquer, votre code devient dépendant de cette classe. Même si cette dépendance ne vous effraie pas, il y a un autre hic. Parfois, vous ne connaissez que l’interface implémentée par l’objet, mais pas sa classe concrète. Si le paramètre d’une méthode accepte tous les objets qui implémentent une interface donnée, vous pouvez rencontrer des problèmes.
Solution
Le patron de conception prototype délègue le processus de clonage aux objets qui vont être copiés. Il déclare une interface commune pour tous les objets qui pourront être clonés. Cette interface vous permet de cloner un objet sans coupler votre code à la classe de cet objet. En général, une telle interface ne contient qu’une seule méthode clone
.
L’implémentation de cette méthode se ressemble dans toutes les classes. Elle crée un objet de la classe actuelle et reporte les valeurs des attributs de l’objet d’origine dans le nouveau. Vous pouvez également copier des attributs privés, car la plupart des langages de programmation autorisent l’accès à des objets d’une même classe.
Un objet qui peut être cloné est appelé un prototype. Lorsque vos objets possèdent des dizaines d’attributs et des centaines de configurations possibles, le clonage est une alternative au sous-classage.
Leur fonctionnement est le suivant : vous créez un ensemble d’objets configurés différemment. Dès que vous avez besoin de l’un de ces objets, vous clonez un prototype plutôt que d’en construire un nouveau.
Analogie
Dans la vie réelle, des prototypes sont utilisés pour exécuter des tests avant de lancer les chaînes de production d’un produit. Dans ce cas toutefois, les prototypes ne font pas partie intégrante de la fabrication, ils ne jouent qu’un rôle passif.
Puisque les prototypes industriels ne se clonent pas eux-mêmes, étudions le processus de la mitose d’une cellule (petit rappel de vos cours de biologie). Une fois la division mitotique terminée, une paire de cellules identiques est créée. La cellule originale sert de prototype et joue un rôle actif dans la création de la copie.
Structure
Implémentation de base
-
L’interface Prototype déclare les méthodes de clonage. Dans la majorité des cas, il n’y a qu’une seule méthode
clone
. -
La classe Prototype Concret implémente la méthode de clonage. En plus de copier les données de l’objet original dans le clone, cette méthode peut gérer des cas particuliers du processus de clonage, comme des objets imbriqués, démêler les dépendances récursives, etc.
-
Le Client peut produire une copie de n’importe quel objet implémentant l’interface prototype.
Implémentation du registre de prototypes
-
Le Registre de Prototypes facilite l’accès aux prototypes fréquemment utilisés. Il stocke un ensemble d’objets préconstruits qui ont déjà été copiés. Le registre de prototypes le plus simple est une table de hachage (hash map)
nom → prototype
. Si vous voulez de meilleurs critères de recherche qu’un simple nom, construisez une version plus robuste du registre.
Pseudo-code
Dans cet exemple, le Prototype produit des copies exactes d’objets géométriques, sans coupler le code à leurs classes.
Toutes les formes implémentent la même interface, et cette dernière fournit une méthode de clonage. Une sous-classe peut appeler la méthode de clonage de son parent avant de copier les valeurs de ses propres attributs dans l’objet.
Possibilités d’application
Utilisez le prototype si vous ne voulez pas que votre code dépende des classes concrètes des objets que vous clonez.
Vous rencontrerez ce cas si votre code manipule des objets obtenus via l’interface d’une application externe. Il arrive que votre code ne puisse pas se baser sur les classes concrètes de ces objets car elles sont inconnues.
Le prototype procure une interface générale au code client et lui permet de travailler avec tous les objets clonables. Cette interface rend le code client indépendant des classes concrètes des objets qu’il clone.
Utilisez ce patron si vous voulez réduire le nombre de sous-classes quand elles ne diffèrent que dans leur manière d’initialiser leurs objets respectifs. Ces sous-classes pourraient avoir été prévues pour créer des objets similaires dans des configurations spécifiques.
Le patron de conception prototype vous permet d’utiliser un ensemble d’objets préconstruits (des prototypes) de différentes manières.
Plutôt que d’instancier une sous-classe qui correspond à une configuration précise, le client peut se contenter de chercher le prototype approprié et de le cloner.
Mise en œuvre
-
Créez l’interface du prototype et déclarez-y la méthode
clone
, mais si vous avez une hiérarchie de classes existante, ajoutez juste la méthode à toutes ses classes. -
Une classe prototype doit définir un autre constructeur capable de prendre un objet de cette classe comme paramètre. Le constructeur doit copier les valeurs des attributs définis dans la classe de l’objet dans l’instance qui vient d’être créée. Si une sous-classe est concernée, vous devez appeler le constructeur du parent pour gérer le clonage de ses attributs privés.
Si le langage de programmation que vous utilisez ne gère pas la surcharge, vous pouvez définir une méthode spéciale pour copier les données de l’objet. Faire tout ceci à l’intérieur du constructeur est plus pratique, car il retourne le résultat directement après avoir appelé l’opérateur
new
. -
La méthode de clonage ne contient qu’une seule ligne de code : un opérateur
new
avec la version prototype du constructeur. Notez bien que chaque classe doit redéfinir la méthode de clonage et utiliser le nom de sa propre classe avec l’opérateurnew
. Si vous ne le faites pas, la méthode de clonage sera capable de produire des objets de la classe mère. -
Vous pouvez également créer un registre centralisé des prototypes pour garder un catalogue de prototypes fréquemment utilisés.
Ce registre peut être implémenté avec une classe fabrique, ou mis dans une classe prototype de base avec une méthode statique qui récupère le prototype. Cette méthode ira chercher un prototype en fonction du critère de recherche que le code client passe à la méthode. Ce critère peut être une chaîne de caractères ou un ensemble de paramètres de recherche. Dès que le prototype recherché est trouvé, le registre crée un clone et le retourne au client.
Pour finir, remplacez tous les appels directs aux constructeurs des sous-classes par des appels à la méthode fabrique du registre de prototypes.
Avantages et inconvénients
- Vous pouvez cloner les objets sans les coupler avec leurs classes concrètes.
- Vous clonez des prototypes préconstruits et vous pouvez vous débarrasser du code d’initialisation redondant.
- Vous pouvez créer des objets complexes plus facilement.
- Vous obtenez une alternative à l’héritage pour gérer les modèles de configuration d’objets complexes.
- Cloner des objets complexes dotés de références circulaires peut se révéler très difficile.
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.
-
Le Prototype se révèle utile lorsque vous voulez sauvegarder des copies de Commandes dans l’historique.
-
Les conceptions qui reposent énormément sur le Composite et le Décorateur tirent des avantages à utiliser le Prototype. Il vous permet de cloner les structures complexes plutôt que de les reconstruire à partir de rien.
-
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.
-
Parfois le Prototype peut venir remplacer le Mémento et proposer une solution plus simple. Cela n’est possible que si l’objet (l’état que vous voulez stocker dans l’historique) est assez direct et ne possède pas de liens vers des ressources externes, ou que les liens sont faciles à recréer.
-
Les Fabriques abstraites, Monteurs et Prototypes peuvent tous être implémentés comme des Singletons.