À partir des modèles de conception "Gang of Four", il y a la méthode Factory:
class Factory(product)
case product
when a
new A
when b
new B
when c
new C
end
new Factory(a)
Pourquoi est-ce plus utile que d'avoir trois classes, a
, b
et c
et de les appeler individuellement?
Parce que votre exemple n'est pas assez compliqué. Pour un scénario aussi simple, il n'est même pas logique d'utiliser un modèle avancé.
Mais si vous devez en savoir plus que le produit pour construire A, B ou C, et que vous ne pouvez pas avoir un accès direct à cette connaissance, alors c'est utile. Ensuite, vous utilisez l'usine pour servir de centre de connaissances pour la production des objets nécessaires.
Peut-être que ces objets ont besoin d'une référence à un objet X, que l'usine peut fournir, mais votre code à l'endroit où vous voulez construire A, B ou C ne peut pas ou ne devrait pas avoir accès à X. Peut-être quand vous avez X vous créez A et B mais si vous avez un type Y, vous créez C.
Considérez également que certains objets peuvent nécessiter 20 dépendances pour être créés; alors quoi? Aller à la recherche de ces dépendances dans un endroit où elles ne devraient pas être accessibles peut être problématique.
Un modèle d'usine est généralement plus complexe que cela. Une usine décide de certains critères quelle instance créer/retourner. Au lieu de cela, lorsque vous n'utilisez pas l'usine, ce code sera utilisé à plusieurs reprises à plusieurs endroits dans votre code.
À titre d'exemple, considérez ce qui suit: vous devez charger des données à partir d'une base de données, mais vous avez une base de données centrale pour l'intégration avec beaucoup de données, et une plus petite en mémoire sur chaque dev-PC. Dans votre code, vous demandez à une usine d'obtenir a DB-handle et l'usine retourne l'une de celles-ci en fonction par exemple de un fichier de configuration.
Le modèle Factory Method résume le processus de prise de décision de la classe appelante. Cela présente plusieurs avantages:
Réutilisation. Si je veux instancier à plusieurs endroits, je n'ai pas à répéter ma condition, donc quand je viens ajouter une nouvelle classe, je ne courez pas le risque d'en manquer un.
Testabilité unitaire. Je peux écrire 3 tests pour l'usine, pour m'assurer qu'il retourne les bons types dans les bonnes conditions, alors ma classe d'appel n'a besoin que à tester pour voir si elle appelle la fabrique puis les méthodes requises sur la classe retournée. Il ne doit rien savoir de l'implémentation de l'usine elle-même ni des classes concrètes.
Extensibilité. Lorsque quelqu'un décide que nous devons ajouter une nouvelle classe D à cette usine, aucun du code appelant, ni tests unitaires ni implémentation, n'a jamais besoin de être dit. Nous créons simplement une nouvelle classe D et étendons notre méthode d'usine. C'est la définition même de Open-Closed Principle .
Vous pouvez même créer une nouvelle classe d'usine et les rendre remplaçables à chaud, si la situation l'exige - par exemple, si vous voulez pouvoir activer et désactiver la classe D pendant le test. Je n'ai rencontré cette situation qu'une seule fois, mais c'était extrêmement utile.
Comme cela a été dit, le Factory Pattern n'est pas toujours le chemin à parcourir. Mais, partout où vous voyez une instanciation conditionnelle, vous devriez y réfléchir un instant.
Les principaux avantages du modèle Factory sont doubles:
Les lieux qui nécessitent une implémentation du produit n'ont pas besoin de savoir comment en construire un. L'usine détient cette information.
Voulez-vous savoir quels arguments passer au constructeur particulier? Ou quelles dépendances devez-vous injecter? Ou comment enregistrer la classe d'implémentation dans la base de données une fois qu'elle est entièrement configurée? Non? Laissez l'usine s'occuper de tout ça.
Les endroits qui nécessitent une implémentation du produit n'ont pas besoin de savoir au moment de la description du module (c'est-à-dire au moment de la compilation) quel est le nom de la classe d'implémentation.
Ainsi, a
n'a rien à voir avec A
; le "quoi construire" peut être décrit en fonction des propriétés non fonctionnelles souhaitées, et pas seulement du nom. C'est beaucoup plus flexible.
L'inconvénient est que lorsque vous savez quoi faire et comment le faire, vous obtenez plus de complexité lorsque vous utilisez une usine. La solution est simple: n'utilisez pas une usine quand cela n'a pas de sens!
Je voudrais penser aux modèles de conception en termes de classes en tant que "personnes", et les modèles sont des façons dont les gens se parlent.
Donc, pour moi, le modèle d'usine est comme une agence d'embauche. Vous avez quelqu'un qui aura besoin d'un nombre variable de travailleurs. Cette personne peut connaître certaines informations dont elle a besoin chez les personnes qu'elle embauche, mais c'est tout.
Ainsi, lorsqu'ils ont besoin d'un nouvel employé, ils appellent l'agence d'embauche et leur disent ce dont ils ont besoin. Maintenant, pour réellement embaucher quelqu'un, vous devez connaître beaucoup de choses - avantages sociaux, vérification de l'admissibilité, etc. Mais la personne qui embauche n'a pas besoin de savoir tout cela - l'agence d'embauche gère tout cela.
De la même manière, l'utilisation d'une usine permet au consommateur de créer de nouveaux objets sans avoir à connaître les détails de leur création, ni quelles sont leurs dépendances - ils n'ont qu'à fournir les informations qu'ils souhaitent réellement.
Le modèle Factory est le modèle de conception le plus utilisé et le plus abusé.
J'ai rencontré de nombreux cas où une classe Factory est codée lorsqu'un simple constructeur serait adéquat.
N'utilisez pas une classe d'usine à moins que: -
Utilisez la méthode Factory lors de l'instanciation d'une sous-classe et le code client n'est pas censé être chargé de décider quelle sous-classe particulière est instanciée.
Il est utile car il vous évite d'avoir à modifier le code client lorsque vous devez changer la classe à instancier. La modification du code existant est une mauvaise pratique car elle est généralement sujette aux erreurs.
Un exemple serait d'avoir des sous-classes, où chacune trie les données dans l'ordre croissant, mais d'une manière différente. Chaque voie est optimale pour un type particulier de données. par exemple: des données partiellement triées, des données qui sont des nombres, etc. Le code client est une classe qui ne gère que l'impression des données. Avoir du code qui décide quelle classe de tri est instanciée dans la classe client en ferait une classe complexe. En d'autres termes, avoir plus d'une responsabilité, dans ce cas, décider quelle classe de tri est optimale et imprimer les données. En plaçant le code qui décide quelle classe de tri est instanciée dans une classe Factory, il sépare les problèmes afin que vous n'ayez pas besoin de changer la classe client à chaque fois que vous devez changer la sous-classe de tri qui est instanciée.
C'est une façon de couvrir votre cul, si vous pouvez prévoir vos changements d'auto-évaluation dans la ligne de la façon dont ou quelle classe sera instanciée, les classes d'usine ont du sens à utiliser. Cela permet de garder vos classes concentrées sur leur seule responsabilité et, par conséquent, vous évite d'avoir à modifier le code existant qui n'est pas lié.