Comment un autre langage populaire éviterait-il d'avoir à utiliser le modèle d'usine tout en gérant une complexité similaire à celle de Java / Java EE?

23

Le modèle d'usine (ou du moins l'utilisation de FactoryFactory..) est le but de nombreuses blagues, comme ici .

En plus d'avoir des noms verbeux et "créatifs" comme RequestProcessorFactoryFactory.RequestProcessorFactory , y a-t-il quelque chose de fondamentalement incorrect avec le modèle d'usine si vous devez programmer en Java / C ++ et qu'il existe un cas d'utilisation pour Abstract_factory_pattern ?

Comment une autre langue populaire (par exemple, Ruby ou Scala ) éviterait-elle d'avoir à l'utiliser tout en gérant une complexité similaire?

La raison pour laquelle je demande, c'est que je vois principalement les critiques des usines mentionnées dans le contexte de l' écosystème Java / Java EE , mais elles n'expliquent jamais comment d'autres langages / cadres les résolvent.

senseiwu
la source
4
Le problème n'est pas l'utilisation d'usines - c'est un modèle parfaitement fin. C'est la surutilisation des usines qui est particulièrement répandue dans le monde java d'entreprise.
CodesInChaos
Très joli lien de blague. J'adorerais voir une interprétation fonctionnelle du langage de la récupération des outils pour construire un rack à épices. Ce serait vraiment le ramener à la maison, je pense.
Patrick M

Réponses:

28

Votre question est étiquetée avec "Java", pas étonnant que vous vous demandez pourquoi le modèle Factory est moqué: Java lui-même est livré avec des abus bien emballés de ce modèle.

Par exemple, essayez de charger un document XML à partir d'un fichier et exécutez une requête XPath sur celui-ci. Vous avez besoin de quelque chose comme 10 lignes de code juste pour configurer les usines et les constructeurs:

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder(); 

Document xmlDocument = builder.parse(new FileInputStream("c:\\employees.xml"));
XPath xPath =  XPathFactory.newInstance().newXPath();

xPath.compile(expression).evaluate(xmlDocument);

Je me demande si les gars qui ont conçu cette API ont déjà travaillé en tant que développeurs ou ont-ils simplement lu des livres et jeté des choses. Je comprends qu'ils ne voulaient tout simplement pas écrire un analyseur eux-mêmes et ont laissé la tâche à d'autres, mais cela rend toujours une implémentation laide.

Puisque vous demandez quelle est l'alternative, voici le chargement du fichier XML en C #:

XDocument xml = XDocument.Load("c:\\employees.xml");
var nodes = xml.XPathSelectElements(expression);

Je soupçonne que les développeurs Java nouvellement éclos voient la folie Factory et pensent que c'est OK pour le faire - si les génies qui ont eux-mêmes construit Java les ont beaucoup utilisés.

L'usine et tous les autres modèles sont des outils, chacun adapté à un travail particulier. Si vous les appliquez à des emplois auxquels ils ne conviennent pas, vous aurez forcément du code laid.

Sten Petrov
la source
1
Juste une note que votre alternative C # utilise la nouvelle LINQ to XML - l'ancienne alternative DOM aurait ressemblé à ceci: XmlDocument xml = new XmlDocument(); xml.Load("c:\\employees.xml"); XmlNodeList nodes = xml.SelectNodes(expression); (Dans l'ensemble, LINQ to XML est beaucoup plus facile à utiliser, mais surtout dans d'autres domaines.) Et voici un article de la base de connaissances J'ai trouvé, avec une méthode recommandée par MS: support.microsoft.com/kb/308333
Bob
36

Comme souvent, les gens comprennent mal ce qui se passe (et cela inclut de nombreux rires).
Ce n'est pas le modèle d'usine en soi qui est mauvais autant que la façon dont beaucoup (peut-être même la plupart) les utilisent.

Et cela découle sans aucun doute de la façon dont la programmation (et les modèles) sont enseignés. Les écoliers (souvent appelés eux-mêmes «étudiants») se font dire de «créer X en utilisant le modèle Y» et après quelques itérations de cela pense que c'est la façon d'aborder tout problème de programmation.
Ils commencent donc à appliquer un modèle spécifique qu'ils aiment à l'école contre tout et n'importe quoi, que ce soit approprié ou non.

Et cela inclut malheureusement les professeurs d'université qui écrivent des livres sur la conception de logiciels.
Le point culminant de ceci était un système que j'avais le mécontentement distinct d'avoir à maintenir qui avait été créé par un tel (l'homme avait même plusieurs livres sur la conception orientée objet à son nom, et un poste d'enseignant dans le département CS d'une grande université ).
Il était basé sur un modèle à 3 niveaux, chaque niveau lui-même étant un système à 3 niveaux (à découpler ...). Sur l'interface entre chaque ensemble de niveaux, des deux côtés, il y avait une usine pour produire les objets pour transférer des données à l'autre niveau, et une usine pour produire l'objet pour traduire l'objet reçu en un objet appartenant à la couche réceptrice.
Pour chaque usine, il y avait une usine abstraite (qui sait, l'usine pourrait devoir changer et vous ne voulez pas que le code d'appel doive changer ...).
Et tout ce gâchis était bien sûr complètement sans papiers.

Le système avait une base de données normalisée à la 5ème forme normale (je ne vous trompe pas).

Un système qui n'était pratiquement rien de plus qu'un carnet d'adresses pouvant être utilisé pour consigner et suivre les documents entrants et sortants, il avait une base de code de 100 Mo en C ++ et plus de 50 tables de base de données. L'impression de 500 lettres types prendrait jusqu'à 72 heures avec une imprimante connectée directement à l'ordinateur exécutant le logiciel.

C'EST POURQUOI les gens se moquent des modèles, et en particulier des gens qui se concentrent sur un seul modèle spécifique.

jwenting
la source
40
L'autre raison est que certains des modèles du Gang of Four sont des hacks vraiment verbeux pour des choses qui peuvent être faites trivialement dans un langage avec des fonctions de première classe, ce qui en fait des anti-modèles inévitables. Les modèles "Strategy", "Observer", "Factory", "Command" et "Template Method" sont des hacks pour passer des fonctions comme arguments; "Visiteur" est un hack pour effectuer une correspondance de modèle sur une union de type somme / variante / étiqueté. Le fait que certaines personnes font beaucoup de bruit pour quelque chose qui est littéralement trivial dans de nombreux autres langages est ce qui conduit les gens à se moquer de C ++, Java et des langages similaires.
Doval
7
Merci pour cette anecdote. Pouvez-vous également élaborer un peu sur "quelles sont les alternatives?" une partie de la question pour ne pas devenir comme ça aussi?
Philipp
1
@Doval Pouvez-vous me dire comment renvoyer la valeur d'une commande exécutée ou d'une stratégie appelée lorsque vous ne pouvez transmettre que la fonction? Et si vous dites des fermetures, gardez à l'esprit que c'est exactement la même chose que la création d'une classe, sauf plus explicite.
Euphoric
2
@ Euphoric Je ne vois pas le problème, pouvez-vous élaborer? En tout cas, ce n'est pas exactement la même chose. Vous pourriez aussi bien dire qu'un objet avec un seul champ est exactement le même qu'un pointeur / référence à une variable ou qu'un entier est exactement le même qu'une énumération (réelle). Il y a des différences dans la pratique: cela n'a aucun sens de comparer des fonctions; Je n'ai pas encore vu de syntaxe concise pour les classes anonymes; et toutes les fonctions avec le même argument et les mêmes types de retour ont le même type (contrairement aux classes / interfaces avec des noms différents, qui sont des types différents même lorsqu'ils sont identiques.)
Doval
2
@Euphoric Correct. Cela serait considéré comme un mauvais style fonctionnel s'il y avait un moyen de faire le travail sans effets secondaires. Une alternative simple serait d'utiliser un type somme / union étiquetée pour renvoyer l'un des N types de valeurs. Quoi qu'il en soit, supposons qu'il n'existe aucun moyen pratique; ce n'est pas la même chose qu'une classe. Il s'agit en fait d'une interface (au sens de la POO). Il n'est pas difficile de voir si vous considérez qu'une paire de fonctions avec les mêmes signatures serait interchangeable, tandis que deux classes ne sont jamais interchangeables même si elles ont exactement le même contenu.
Doval
17

Les usines présentent de nombreux avantages qui permettent de concevoir des applications élégantes dans certaines situations. La première est que vous pouvez définir les propriétés des objets que vous souhaitez créer ultérieurement en un seul endroit en créant une usine, puis remettre cette usine. Mais souvent, vous n'avez pas vraiment besoin de le faire. Dans ce cas, l'utilisation d'une usine ajoute simplement une complexité supplémentaire sans vous donner quoi que ce soit en retour. Prenons cette usine, par exemple:

WidgetFactory redWidgetFactory = new ColoredWidgetFactory(COLOR_RED);
Widget widget = redWidgetFactory.create();

Une alternative au modèle Factory est le modèle Builder très similaire. La principale différence est que les propriétés des objets créés par une fabrique sont définies lorsque la fabrique est initialisée, tandis qu'un générateur est initialisé avec un état par défaut et toutes les propriétés sont définies par la suite.

WidgetBuilder widgetBuilder = new WidgetBuilder();
widgetBuilder.setColor(COLOR_RED);
Widget widget = widgetBuilder.create();

Mais lorsque la suringénierie est votre problème, le remplacement d'une usine par un constructeur n'est probablement pas une grande amélioration.

Le remplacement le plus simple de l'un ou l'autre modèle est bien sûr de créer des instances d'objet avec un constructeur simple avec l' newopérateur:

Widget widget = new ColoredWidget(COLOR_RED);

Les constructeurs, cependant, ont un inconvénient crucial dans la plupart des langages orientés objet: ils doivent renvoyer un objet de cette classe exacte et ne peuvent pas retourner un sous-type.

Lorsque vous devez choisir le sous-type lors de l'exécution mais que vous ne souhaitez pas recourir à la création d'une nouvelle classe Builder ou Factory pour cela, vous pouvez utiliser une méthode d'usine à la place. Il s'agit d'une méthode statique d'une classe qui renvoie une nouvelle instance de cette classe ou l'une de ses sous-classes. Une usine qui ne maintient aucun état interne peut souvent être remplacée par une telle méthode d'usine:

 Widget widget = Widget.createColoredWidget(COLOR_RED); // returns an object of class RedColoredWidget

Une nouvelle fonctionnalité de Java 8 sont les références de méthodes qui vous permettent de transmettre des méthodes, comme vous le feriez avec une fabrique sans état. Idéalement, tout ce qui accepte une référence de méthode accepte également tout objet qui implémente la même interface fonctionnelle, qui peut également être une usine à part entière avec un état interne, de sorte que vous pouvez facilement introduire des usines plus tard, lorsque vous voyez une raison de le faire.

Philipp
la source
2
Une usine peut souvent également être configurée après sa création. La différence majeure, afaik, pour un constructeur est qu'un constructeur est généralement utilisé pour créer une seule instance complexe, tandis que les usines sont utilisées pour créer de nombreuses instances similaires.
Céphalopode
1
merci (+1), mais la réponse ne parvient pas à expliquer comment d'autres PL le résoudraient, néanmoins merci d'avoir signalé les références de la méthode Java 8.
senseiwu
1
J'ai souvent pensé qu'il serait logique dans un cadre orienté objet de limiter la construction d'objet réelle au type en question, et que cela foo = new Bar(23);soit équivalent à foo = Bar._createInstance(23);. Un objet doit pouvoir interroger de manière fiable son propre type à l'aide d'une getRealType()méthode privée , mais les objets doivent pouvoir désigner un supertype à renvoyer par des appels externes à getType(). Le code extérieur ne devrait pas se soucier de savoir si un appel à new String("A")renvoie réellement une instance de String[par opposition à par exemple une instance de SingleCharacterString].
supercat
1
J'utilise beaucoup d'usines de méthode statique lorsque je dois choisir une sous-classe en fonction du contexte, mais les différences doivent être opaques pour l'appelant. Par exemple, j'ai une série de classes d'images qui contiennent toutes draw(OutputStream out)mais génèrent du HTML légèrement différent, la fabrique de méthodes statiques crée la bonne classe pour la situation, puis l'appelant peut simplement utiliser la méthode draw.
Michael Shopsin
1
@supercat vous pourriez être intéressé à jeter un oeil à objective-c. La construction d'objets consiste généralement en de simples appels de méthode [[SomeClass alloc] init]. Il est possible de renvoyer une instance de classe complètement différente ou un autre objet (comme une valeur mise en cache)
axelarge
3

Les usines d'une sorte ou d'une autre se trouvent dans à peu près n'importe quel langage orienté objet dans des circonstances appropriées. Parfois, vous avez simplement besoin d'un moyen de sélectionner le type d'objet à créer en fonction d'un paramètre simple comme une chaîne.

Certaines personnes vont trop loin et essaient d'architecturer leur code pour ne jamais avoir besoin d'appeler un constructeur sauf à l'intérieur d'une usine. Les choses commencent à devenir ridicules lorsque vous avez des usines.

Ce qui m'a frappé quand j'ai appris Scala, et je ne connais pas Ruby, mais je pense que c'est à peu près la même chose, c'est que le langage est suffisamment expressif pour que les programmeurs n'essaient pas toujours de pousser le travail de "plomberie" vers des fichiers de configuration externes . Plutôt que d'être tenté de créer des usines pour relier vos classes dans différentes configurations, dans Scala, vous utilisez des objets avec des traits mélangés. Il est également relativement facile de créer de simples DSL en langage pour des tâches qui sont souvent bien trop sur-conçues en Java.

De plus, comme d'autres commentaires et réponses l'ont souligné, les fermetures et les fonctions de première classe évitent la nécessité de nombreux modèles. Pour cette raison, je pense que de nombreux anti-patterns commenceront à disparaître à mesure que C ++ 11 et Java 8 seront généralisés.

Karl Bielefeldt
la source
merci (+1). Votre réponse explique certains de mes doutes quant à la manière dont Scala pourrait l'éviter. Honnêtement, ne pensez-vous pas qu'une fois (si) Scala devient au moins la moitié aussi populaire que Java au niveau de l'entreprise, des armées de frameworks, de modèles, de serveurs d'applications payants, etc. apparaîtront?
senseiwu
Je pense que si Scala devait dépasser Java, ce serait parce que les gens préfèrent la "façon Scala" d'architecturer les logiciels. Ce qui me semble le plus probable, c'est que Java continue d'adopter plus de fonctionnalités qui poussent les gens à passer à Scala, comme les génériques de Java 5 et les lambdas et les flux de Java 8, ce qui, espérons-le, conduira éventuellement les programmeurs à abandonner les anti-modèles qui ont surgi lorsque ces les fonctionnalités n'étaient pas disponibles. Il y aura toujours des adhérents FactoryFactory, peu importe la qualité des langues. De toute évidence, certaines personnes aiment ces architectures, ou elles ne seraient pas si courantes.
Karl Bielefeldt
2

En général, il y a cette tendance que les programmes Java sont horriblement sur-conçus [citation nécessaire]. La présence de nombreuses usines est l'un des symptômes les plus courants de la suringénierie; c'est pourquoi les gens s'en moquent.

En particulier, le problème que Java a avec les usines est qu'en Java a) les constructeurs ne sont pas des fonctions et b) les fonctions ne sont pas des citoyens de première classe.

Imaginez que vous pourriez écrire quelque chose comme ça ( Functionsoit une interface bien connue du JRE )

// Framework code
public Node buildTree(Function<BranchNode, Node, Node> branchNodeFactory) {
    Node current = nextNode();
    while (hasNextNode()) {
        current = branchNodeFactory.call(current, nextNode());
    }
    return current
}

// MyDataNode.java
public class MyBranchNode implements BranchNode {

    public MyBranchNode(Node left, Node right) { ... }
}

// Client code
Node root = buildTree(MyBranchNode::new);

Voir, pas d'interfaces ou de classes d'usine. De nombreux langages dynamiques ont des fonctions de première classe. Hélas, cela n'est pas possible avec Java 7 ou une version antérieure (la mise en œuvre de la même chose avec les usines est laissée au lecteur comme exercice).

Ainsi, l'alternative n'est pas tant «d'utiliser un modèle différent», mais plutôt «d'utiliser un langage avec un meilleur modèle d'objet» ou de «ne pas sur-concevoir de simples problèmes».

Céphalopode
la source
2
cela ne tente même pas de répondre à la question posée: "quelles sont les alternatives?"
moucher
1
Lisez le deuxième paragraphe et vous arriverez à la partie «comment les autres langues le font». (PS: l'autre réponse ne montre pas non plus d'alternatives)
Céphalopode
4
@gnat N'est-il pas indirectement en train de dire que l'alternative est d'utiliser des fonctions de première classe? Si ce que vous voulez est une boîte noire qui peut créer des objets, vos options sont soit une usine ou une fonction, et une usine n'est qu'une fonction déguisée.
Doval
3
"Dans de nombreux langages dynamiques" est un peu trompeur, car ce qui est réellement nécessaire est un langage avec des fonctions de première classe. "Dynamique" est orthogonal à cela.
Andres F.
C'est vrai, mais c'est une propriété souvent trouvée dans les langages dynamiques. J'ai mentionné la nécessité d'une fonction de première classe au début de ma réponse.
Céphalopode