Le modèle d'usine viole-t-il le principe ouvert / fermé?

14

Pourquoi cette ShapeFactory utilise-t-elle des instructions conditionnelles pour déterminer quel objet instancier. Ne devons-nous pas modifier ShapeFactory si nous voulons ajouter d'autres classes à l'avenir? Pourquoi cela ne viole-t-il pas le principe ouvert et fermé?

Conception de modèle d'usine

ShapeFactory Design

Armon Safai
la source
2
De quel «modèle d'usine» parlez-vous précisément? En général, une fabrique est un objet ou une méthode qui sert à instancier un objet. Ensuite, il existe des variantes spécifiques de cette idée générale, telles que le modèle d'usine abstraite, où chaque instance d'usine représente une palette de choix spécifique - généralement gérée via des sous-classes plutôt que des conditions.
amon
3
Merci pour ces informations, cela clarifie beaucoup les choses. C'est définitivement un exemple d'un modèle d'usine, mais pas du modèle d'usine abstrait communément associé aux modèles d'usine. Le code de cet article est assez discutable, et je ne m'attendrais pas à voir quelque chose comme ça dans du vrai code.
amon
@ArmonSafai: Vous liez beaucoup ce billet de blog, mais vous n'expliquez pas vraiment pourquoi. Sommes-nous tous en quelque sorte ignorants du schéma? Nous avons aussi Google, tout comme vous.
Robert Harvey du
1
@RobertHarvey Je relie cet article de blog pour montrer comment le modèle d'usine de cette page utilise des conditions
Armon Safai

Réponses:

20

La sagesse conventionnelle orientée objet consiste à éviter les ifdéclarations et à les remplacer par une répartition dynamique des méthodes remplacées dans les sous-classes d'une classe abstraite. Jusqu'ici tout va bien.

Mais le but du modèle d'usine est de vous éviter d'avoir à connaître les sous-classes individuelles et de travailler uniquement avec la superclasse abstraite . L'idée est que l'usine sait mieux que vous quelle classe spécifique à instancier, et vous feriez mieux de travailler uniquement avec les méthodes publiées par la super classe. C'est souvent vrai et c'est un modèle valable.

Par conséquent, l'écriture d'une classe d'usine ne peut en aucun cas renoncer aux ifinstructions. Cela déplacerait le fardeau du choix d'une classe spécifique vers l'appelant, ce qui est exactement ce que le modèle est censé éviter. Tous les principes ne sont pas absolus (en fait, aucun principe n'est absolu), et si vous utilisez ce modèle, vous supposeriez que l'avantage en est supérieur à l'avantage de ne pas utiliser un if.

Kilian Foth
la source
2
Il est parfaitement possible de créer un modèle d'usine sans beaucoup de ifs. Voir la réponse de @ BЈовић pour un exemple simple de comment y parvenir. Voté.
David Arno
11
@DavidArno Naturellement, il existe différentes façons de choisir la classe concrète. Service Locator en est un, un conteneur IoC configurable en est un autre. Ce ne sont que des détails de mise en œuvre; ils ne portent pas atteinte au message principal de Killian, à savoir que la Factory soulage l'appelant d'avoir à décider quelle classe concrète instancier. Ne vous embourbez pas dans les détails.
Robert Harvey
1
Une déclaration formidable qui ne répond en rien à la question.
Martin Maat
1
@ R.Schmitz Je pense que vous avez tort dans votre hypothèse. Je pense que beaucoup de gens ont raté cette question de l'OP: "Ne devons-nous pas modifier ShapeFactory si nous voulons ajouter d'autres classes à l'avenir?" CLAIREMENT, l'OP est confus en pensant que ce modèle viole OCP car pour ajouter de nouvelles fonctionnalités, vous devez modifier le code existant. La bonne réponse à cette question se trouve dans ma réponse. La réponse courte: vous laissez ce code seul et vous appliquez le modèle Abstract Factory pour ÉTENDRE (pas modifier) ​​votre fonctionnalité existante. Pour cette raison, la réponse de Kilian NE RÉPOND PAS à la question.
hfontanez
5

L'exemple utilise probablement une instruction conditionnelle car c'est la plus simple. Une implémentation plus complexe pourrait utiliser une carte ou une configuration ou (si vous voulez vraiment être fantaisiste) une sorte de registre où les classes peuvent s'enregistrer. Cependant, il n'y a rien de mal à utiliser un conditionnel si le nombre de classes est petit et change rarement.

Prolonger le conditionnel pour ajouter le soutien à une nouvelle sous-classe à l'avenir serait en effet à proprement parler une violation du principe ouvert / fermé. La solution "correcte" serait de créer une nouvelle usine avec la même interface. Cela dit, l'adhésion au principe O / C doit toujours être mise en balance avec d'autres principes de conception comme KISS et YAGNI.

Cela dit, le code affiché est clairement un exemple de code conçu pour montrer le concept d'une usine et rien d'autre. Par exemple, c'est vraiment un mauvais style de retourner null comme l'exemple le fait, mais une gestion d'erreur plus élaborée ne ferait qu'obscurcir le point. Un exemple de code n'est pas un code de qualité de production, vous ne devriez pas vous y attendre.

JacquesB
la source
Pouvez-vous expliquer comment une carte / configuration / registre fonctionnerait?
Armon Safai
@ArmonSafai: Voici un exemple: jkfill.com/2010/12/29/self-registering-factories-in-c-sharp
JacquesB
Les usines d'auto-enregistrement sont, AFAIK, impossibles en C ++ à l'intérieur des bibliothèques statiques car les variables globales inutilisées (c'est-à-dire utilisées par odr) sont rejetées par les chaînes d'outils.
void.pointer
@ArmonSafai lire ceci pour plus de compréhension goo.gl/RYuNSM
AZ_
2

Le motif lui-même ne viole pas le principe ouvert / fermé (OCP). Cependant, nous violons l'OCP lorsque nous utilisons le modèle de manière incorrecte.

La réponse simple à cette question est la suivante:

  1. Créez votre fonctionnalité de base à l'aide du modèle de méthode d'usine .
  2. PROLONGEZ vos fonctionnalités en utilisant le modèle d' usine abstraite

Dans l'exemple fourni, votre fonctionnalité de base prend en charge trois formes: cercle, rectangle et carré. Supposons que vous deviez prendre en charge Triangle, Pentagone et Hexagone à l'avenir. Pour ce faire SANS violer l'OCP, vous devez créer une usine supplémentaire pour prendre en charge vos nouvelles formes (appelons-le AdvancedShapeFactory), puis utiliser AbstractFactory pour décider quelle usine vous devez créer afin de créer les formes dont vous avez besoin.

hfontanez
la source
Je préfère de loin la solution des usines auto-enregistrées (en l'absence d'un véritable conteneur IoC configurable qui est le meilleur), car sinon ce que nous obtenons de votre proposition sont essentiellement des usines d'usines , et c'est à ce moment-là que les choses deviennent un peu trop complexes.
Nom1fan
1

Si vous parlez du modèle de l'usine abstraite, la prise de décision n'est souvent pas dans l'usine elle-même mais dans le code d'application. C'est ce code qui choisit quelle usine concrète instancier et transmettre au code client qui utilisera les objets produits par l'usine. Voir la fin de l'exemple Java ici: https://en.wikipedia.org/wiki/Abstract_factory_pattern

La prise de décision n'implique pas nécessairement des ifdéclarations. Il pourrait lire le type concret Factory à partir d'un fichier de configuration, le dériver d'une structure de carte, etc.

guillaume31
la source
Si moi, l'appelant, je décide quelle classe concrète instancier, alors pourquoi me tracasse-t-on avec une usine abstraite?
Robert Harvey du
Veuillez définir "l'appelant". Comme je le décris dans ma réponse, il y a le code d'application global, puis le code qui doit générer des objets à l'aide d'une usine. Alors que ce dernier doit en effet ignorer la classe concrète à instancier, un autre code contextuel doit le connaître et le renouveler ...
guillaume31
0

Si vous pensez à l'Open-Close au niveau de la classe avec cette usine, vous créez une autre classe dans votre système Open-Close, par exemple si vous avez une autre classe qui prend une forme et calcule la zone (exemple typique), cette classe est OpenClose car il peut calculer l'aire de nouveaux types de formes sans modification. Ensuite, vous avez une autre classe qui dessine la forme, une autre classe qui prend N formes et renvoie la plus grande et vous pouvez penser en général que les autres classes de votre système qui traitent les formes sont Open-Close (au moins sur les formes). En regardant la conception, l'usine permet au reste du système d'être ouvert-fermé et bien sûr l'usine elle-même n'est PAS ouverte-fermée.

Bien sûr, vous pouvez également ouvrir / fermer cette usine, via une sorte de chargement dynamique et votre système entier peut être ouvert-fermé (vous pouvez ajouter de nouvelles formes en déposant un pot dans le chemin de classe par exemple). Vous devez évaluer si cette complexité supplémentaire vaut selon le système que vous construisez, tous les systèmes n'ont pas besoin de fonctionnalités enfichables et tout le système n'a pas besoin d'être complètement ouvert-fermé.

AlfredoCasado
la source
0

Le principe ouvert-fermé, comme le principe de substitution de Liskov, s'applique aux arbres de classes, aux hiérarchies d'héritage. Dans votre exemple, la classe d'usine ne se trouve pas dans l'arbre généalogique des classes qu'elle instancie donc elle ne peut pas violer ces règles. Il y aurait une violation si votre GetShape (ou plus convenablement nommé, CreateShape) était implémenté dans la classe de base Shape.

Martin Maat
la source
-2

Tout dépend de la façon dont vous l'implémentez. Vous pouvez utiliser std::mappour maintenir des pointeurs de fonction vers des fonctions créant des objets. Ensuite, le principe d'ouverture / fermeture n'est pas violé. Ou interrupteur / boîtier.

Quoi qu'il en soit, si vous n'aimez pas le modèle d'usine, vous pouvez toujours utiliser l'injection de dépendance.

BЈовић
la source
6
Comment le commutateur / boîtier est-il meilleur que les conditionnels? L'utilisation d'une carte / dict / table pour représenter le code comme des données est bonne, si vous avez réellement besoin d'un registre de différentes implémentations - par exemple dans certaines implémentations de conteneurs DI. Mais avoir des rappels différents du même type n'est pas nécessaire pour la plupart des usines! Je ne comprends pas très bien pourquoi vous suggérez cela. De plus, de nombreux conteneurs DI sont implémentés en termes d'objets d'usine, donc suggérer d'utiliser DI au lieu d'usines semble un peu circulaire.
amon
1
@amon Je voulais utiliser d'autres types de DI - pas l'usine.
BЈовић
1
Comment votre usine va-t-elle décider quel pointeur utiliser? Finalement, vous devez prendre une décision.
whatsisname
@ArmonSafai ???
BЈовић