Le thème des modèles de conception est assez populaire. De nombreuses vidéos ont été tournées dessus et des articles ont été écrits. Combine tous ces matériaux avec un «anti-motif». Complexité accidentelle. En conséquence, les exemples sont abstraits, la description prête à confusion, la manière de postuler n'est pas claire. Et la tâche principale des modèles de conception - la simplification (du code et du travail en général) n'est pas réalisée. Après tout, l'utilisation du modèle nécessite des efforts supplémentaires. À peu près la même chose que les tests unitaires.
Je vais essayer d'expliquer les modèles de conception en termes de comment les appliquer, où et pourquoi.
Six générateurs peuvent être attribués à:
- Prototype
- Usine abstraite,
- Méthode d'usine
- Constructeur
- Singleton
- Initialisation paresseuse.
Tous les autres modèles relatifs aux générateurs sont un cas particulier d'application et il est inutile de s'y attarder.
La génération de modèles peut être divisée en trois groupes, sur la question à laquelle ils répondent. Donc, trois questions:
O??
Trois modèles répondent à cette question: prototype, usine abstraite et méthode d'usine.
Un peu sur les termesDans le cadre du concept de POO, il n'y a que trois endroits où il est théoriquement possible de générer une nouvelle instance.
- Le produit est la classe qui est instanciée.
- Le client est la classe qui utilisera l'instance instanciée.
- Partenaire - toute troisième classe dans le domaine de la visibilité du Client.
En fait, ces modèles déterminent le lieu de génération. De plus, ils sont connectés hiérarchiquement et ont une portée différente. La connexion est indiquée sur la figure, les flèches déterminent le sens des appels.

Dans sa mise en œuvre, la «méthode Factory» peut déléguer la génération d'une instance à la «Factory» existante ou au «prototype». Le «prototype», cependant, ne devrait dépendre de personne et tout faire tout seul. Maintenant plus en détail.
"Prototype"
Ce modèle correspond à la place "Produit", en fait, est le constructeur de la classe. Par conséquent, une instance d'une classe spécifique (précédemment connue) est toujours générée.
Dans le cadre de ce modèle, le constructeur ne connaît que les paramètres qui lui sont directement transmis (le nombre de paramètres tend vers le nombre de champs de classe). Bien sûr, il y a un accès complet à tous les champs et propriétés de la classe créée.
Des méthodes correctement mises en œuvre du "Prototype" vous permettent de vous débarrasser des méthodes d'initialisation supplémentaires en public. À son tour, l'interface externe de la classe devient plus facile et moins tentante d'utiliser la classe à d'autres fins.
Ce que ce modèle nous donne:
- Faible connectivité - la classe ne se connaît que, ne dépend pas de données externes;
- Extensibilité - les constructeurs peuvent être redéfinis ou ajoutés aux descendants;
Des inconvénients:
- Pour les classes complexes, vous devrez peut-être passer de nombreux paramètres pour l'initialisation. Bien qu'il existe une solution triviale.
- Une utilisation directe dans le client peut nuire à la lisibilité et bloquer pratiquement la possibilité de remplacer le type d'instance généré dans le descendant du client.
Le modèle le plus populaire. Tout le monde l'utilise, mais peu savent ce qu'il utilise. C'est bon jusqu'à ce que le premier modèle de travail soit obtenu, jusqu'à ce que les classes et leurs relations soient complètement définies. Après cela, le traitement et l'augmentation de l'abstraction sont obligatoires.
"Usine abstraite"
Un partenaire de classe. Il peut être spécialisé ou "combiner". Peut être statique (pas d'instance). Un exemple de «combinaison» peut être une classe de configuration. Il peut également être caché derrière la façade.
"Factory" voit généralement tous les paramètres globaux de l'application (ou d'un sous-système séparé). La génération immédiate peut être déléguée au prototype. Dans le même temps, le nombre de paramètres d'entrée dans la méthode Factory sera inférieur à celui d'un constructeur de prototype similaire. L'usine ne décide pas qui créer en fonction des paramètres entrants.
Ce modèle est très pratique et facile à mettre en œuvre, mais nécessite une conception préliminaire. Si vous créez des usines pour tout, cela compliquera le code. En fait, nous obtenons un analogue du prototype, mais nous sommes passés à une classe tierce.
Des pros:
- Bonne redéfinition des descendants
- Appel simplifié
- Sur la base de la Factory, il est facile d'implémenter une substitution (modèle State)
Mais il y a aussi des inconvénients:
- Il nécessite une conception, en particulier pour les usines universelles (qui sont utilisées dans de nombreux projets). En d'autres termes, il n'est pas facile d'obtenir immédiatement une bonne usine.
- Il est très facile de gâcher le code, il y a deux domaines principaux:
- Glissant vers le prototype, mais dans une classe extérieure. Les méthodes sont surchargées de paramètres; il existe de nombreuses méthodes elles-mêmes. En conséquence, l'héritage est difficile, à la fois dans l'usine elle-même et dans le client.
- Usine avec méthode universelle. Cette méthode renvoie n'importe quelle instance en fonction des paramètres transmis. Le résultat, comme dans le premier cas.
Très populaire. Ce modèle est utilisé par ceux qui ont suivi le cours du GoF. En règle générale, le code devient encore pire qu'avant d'appliquer les modèles.
Cela a du sens lorsque des usines apparaissent lors de la première révision du code. À ce stade, les combinaisons de paramètres pour les instances créées sont déjà connues et il ne sera pas difficile d'écrire des méthodes Factory généralisées. En conséquence, les appels dans le client seront simplifiés.
Dans certains cas, il est pratique de cacher les usines derrière la façade. Par exemple, l'application possède une douzaine de ses usines et une douzaine de bibliothèques. Pour eux, vous pouvez construire une façade. Cela permettra de ne pas lier les bibliothèques à chaque module, et il est également facile de remplacer une usine par une autre.
Méthode d'usine
Le sommet de l'abstraction dans les modèles génératifs. Lieu d'origine Client. La classe dans laquelle chaque produit est placé dans la méthode d'usine a toutes les chances d'une longue durée de vie. Si sans fanatisme, alors l'axe de développement supposé doit nécessairement être basé sur ce modèle.
La méthode d'usine ne voit pas au-delà de sa classe. Le nombre de paramètres transmis directement doit être minimal (dans la limite de zéro). La méthode elle-même doit être construite en tenant compte de la possibilité de chevauchement dans le descendant.
Une erreur courante est l'initialisation compliquée dans une méthode. Par exemple, lors de la création d'une instance complexe (le modèle Builder), la création de toutes les parties du futur objet est placée dans une seule méthode. En conséquence, une telle méthode est difficile à chevaucher chez le descendant.
Des pros:
- Il sera facile de faire correspondre la méthode du modèle de modèle
- Nous obtenons un code concis dans lequel la logique est clairement visible (elle n'a pas besoin d'être consultée parmi un tas de méthodes et de paramètres)
Il n'y a essentiellement aucun inconvénient.
Ce modèle n'est presque jamais utilisé. En règle générale, il ne peut être vu que dans les projets avec une élaboration préliminaire approfondie. Idéal lorsque la méthode d'usine délègue la génération à l '"usine" ou au "prototype".
Petit exemple
Nous avons une classe pour se connecter à un fichier sur le disque dur. Voici à quoi pourraient ressembler les méthodes génériques dans les modèles «Où?»:
Prototype:
constructor Create(aFilename: string; aLogLevel: TLogLevel);
Tout ce que le concepteur doit savoir lui est transmis sous forme de paramètres.
Usine:
function GetLogger(aLogLevel: TLogLevel): ILogger;
L'usine sait dans quel fichier écrire, comme spécifié dans les paramètres de l'application.
Méthode d'usine:
function NewLogger: ILogger;
Dans la classe Client, on sait avec quels détails se connecter.
Dans cette conception, pour remplacer la classe de journalisation par un stub, il suffit de redéfinir NewLogger dans le descendant du client. Ceci est utile lors des tests unitaires.
Pour se connecter à la base de données, il suffit de remplacer la méthode GetLogger dans le descendant de Factory.