Wie würde eine andere beliebte Sprache vermeiden, das Factory-Muster zu verwenden, während sie eine ähnliche Komplexität wie in Java / Java EE verwaltet?


23

Factory-Pattern (oder zumindest die Verwendung von FactoryFactory..) ist der Kern vieler Witze, wie hier .

Abgesehen von ausführlichen und "kreativen" Namen wie " RequestProcessorFactoryFactory.RequestProcessorFactory" ist das Factory-Muster grundlegend falsch, wenn Sie in Java / C ++ programmieren müssen und es einen Verwendungszweck für " Abstract_factory_pattern" gibt .

Wie würde eine andere beliebte Sprache (zum Beispiel Ruby oder Scala ) es vermeiden, sie zu verwenden, während sie eine ähnliche Komplexität bewältigt?

Der Grund, den ich frage, ist, dass ich hauptsächlich die Kritik an Fabriken sehe, die im Kontext des Java / Java EE- Ökosystems erwähnt wurden, aber sie erklären nie, wie andere Sprachen / Frameworks sie lösen.



4
Das Problem ist nicht die Verwendung von Fabriken - es ist ein perfektes Muster. Es ist die Überbeanspruchung von Fabriken, die in der Enterprise Java-Welt besonders verbreitet ist.
CodesInChaos

Sehr schöner Witzlink. Ich würde gerne eine funktionale Sprachinterpretation sehen, bei der die Werkzeuge zum Aufbau eines Gewürzregals abgerufen werden. Das würde es wirklich nach Hause bringen, denke ich.
Patrick M

Antworten:


28

Ihre Frage ist mit "Java" markiert. Kein Wunder, dass Sie sich fragen, warum das Factory-Muster verspottet wird: Java selbst wird mit einem gut verpackten Missbrauch dieses Musters ausgeliefert.

Versuchen Sie beispielsweise, ein XML-Dokument aus einer Datei zu laden und eine XPath-Abfrage für diese Datei auszuführen. Sie benötigen etwa 10 Codezeilen, um die Factories und Builder einzurichten:

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);

Ich frage mich, ob die Leute, die diese API entworfen haben, jemals als Entwickler gearbeitet haben oder nur Bücher gelesen und Dinge herumgeworfen haben. Ich verstehe, dass sie selbst keinen Parser schreiben wollten und die Aufgabe anderen überließen, aber es ist trotzdem eine hässliche Implementierung.

Da Sie sich fragen, was die Alternative ist, laden Sie hier die XML-Datei in C #:

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

Ich vermute, neu geschlüpfte Java-Entwickler sehen den Factory-Wahnsinn und denken, dass es in Ordnung ist, das zu tun - wenn die Genies, die Java selbst gebaut haben, sie so oft benutzt haben.

Factory und alle anderen Muster sind Werkzeuge, die jeweils für eine bestimmte Aufgabe geeignet sind. Wenn Sie sie auf Jobs anwenden, sind sie nicht dafür geeignet, hässlichen Code zu haben.


1
Nur eine Anmerkung, dass Ihre C # -Alternative die neuere LINQ to XML-Version verwendet - die ältere DOM-Alternative hätte ungefähr so ​​ausgesehen: XmlDocument xml = new XmlDocument(); xml.Load("c:\\employees.xml"); XmlNodeList nodes = xml.SelectNodes(expression); (Insgesamt ist LINQ to XML viel einfacher zu verwenden, wenn auch hauptsächlich in anderen Bereichen.) Und hier ist ein KB-Artikel Ich fand mit einer von MS empfohlenen Methode: support.microsoft.com/kb/308333
Bob

36

Wie so oft missverstehen die Leute, was los ist (und das schließt viele der Lacher ein).
Es ist nicht das Fabrikmuster an sich, das so schlecht ist wie die Art und Weise, wie viele (vielleicht sogar die meisten) Leute es benutzen.

Und das ergibt sich zweifellos aus der Art und Weise, wie Programmieren (und Muster) gelehrt wird. Schulkinder (die sich oft als "Schüler" bezeichnen) werden angewiesen, "X mit dem Muster Y zu erstellen". Nach einigen Iterationen wird davon ausgegangen, dass dies der Weg ist, um Programmierprobleme anzugehen.
Also wenden sie ein bestimmtes Muster an, das ihnen in der Schule gefällt, und zwar gegen alles und gegen alles, ob es angemessen ist oder nicht.

Und dazu gehören auch Universitätsprofessoren, die leider Bücher über Softwaredesign schreiben.
Der Höhepunkt war ein System, das ich ausgesprochen unangenehm pflegen musste (der Mann besaß sogar mehrere Bücher über objektorientiertes Design und eine Lehrtätigkeit in der CS-Abteilung einer großen Universität) ).
Es basierte auf einem 3-Tier-Muster, wobei jedes Tier selbst ein 3-Tier-System war (muss entkoppelt werden ...). Auf der Schnittstelle zwischen jedem Satz von Schichten befand sich auf beiden Seiten eine Fabrik, um die Objekte zu produzieren, um Daten an die andere Schicht zu übertragen, und eine Fabrik, um das Objekt zu produzieren, um das empfangene Objekt in eines zu übersetzen, das zur empfangenden Schicht gehört.
Für jede Factory gab es eine abstrakte Factory (wer weiß, die Factory muss sich möglicherweise ändern und dann soll sich der aufrufende Code nicht ändern ...).
Und das ganze Durcheinander war natürlich völlig undokumentiert.

Das System hatte eine Datenbank, die auf die 5. Normalform normalisiert war (ich mache mir nichts vor).

Ein System, das im Wesentlichen nur ein Adressbuch war, mit dem eingehende und ausgehende Dokumente protokolliert und verfolgt werden konnten. Es verfügte über eine Codebasis von 100 MB in C ++ und über 50 Datenbanktabellen. Das Drucken von 500 Serienbriefen dauert bis zu 72 Stunden, wenn ein Drucker verwendet wird, der direkt an den Computer angeschlossen ist, auf dem die Software ausgeführt wird.

Das ist der Grund, warum sich die Leute über Muster lustig machen und sich speziell auf ein bestimmtes Muster konzentrieren.


40
Der andere Grund ist, dass einige der Gang of Four-Muster nur sehr ausführliche Hacks für Dinge sind, die trivial in einer Sprache mit erstklassigen Funktionen ausgeführt werden können, was sie zu unvermeidbaren Anti-Mustern macht. Die Muster "Strategy", "Observer", "Factory", "Command" und "Template Method" sind Hacks, um Funktionen als Argumente zu übergeben. "Besucher" ist ein Hack, um eine Musterübereinstimmung für einen Summentyp / eine Variante / eine markierte Vereinigung durchzuführen. Die Tatsache, dass einige Leute viel Lärm um etwas machen, das in vielen anderen Sprachen buchstäblich trivial ist, führt dazu, dass sich die Leute über C ++, Java und ähnliche Sprachen lustig machen.
Doval

7
Vielen Dank für diese Anekdote. Können Sie auch ein wenig auf die "Was sind die Alternativen?" Teil der Frage, damit wir nicht auch so werden?
Philipp

1
@Doval Kannst du mir sagen, wie du den Wert eines ausgeführten Befehls oder einer aufgerufenen Strategie zurückgibst, wenn du nur die Funktion übergeben kannst? Und wenn Sie Closures sagen, dann denken Sie daran, dass es genau das gleiche ist wie das Erstellen einer Klasse, außer dass es expliziter ist.
Euphoric

2
@Euphoric Ich sehe das Problem nicht, kannst du das näher erläutern? Auf jeden Fall sind sie nicht genau dasselbe. Sie können genauso gut sagen, dass ein Objekt mit einem einzelnen Feld genau das gleiche ist wie ein Zeiger / eine Referenz auf eine Variable oder dass eine Ganzzahl genau das gleiche ist wie eine (echte) Aufzählung. In der Praxis gibt es Unterschiede: Es macht keinen Sinn, Funktionen zu vergleichen. Ich habe noch keine prägnante Syntax für anonyme Klassen gefunden. und alle Funktionen mit dem gleichen Argument und Rückgabetypen haben den gleichen Typ (im Gegensatz zu Klassen / Interfaces mit unterschiedlichen Namen, die unterschiedliche Typen sind, auch wenn sie identisch sind.)
Doval

2
@Euphoric Richtig. Das wäre ein schlechter funktioneller Stil, wenn es eine Möglichkeit gäbe, die Arbeit ohne Nebenwirkungen zu erledigen. Eine einfache Alternative wäre die Verwendung eines Summentyps / einer mit Tags versehenen Vereinigung, um einen von N Arten von Werten zurückzugeben. Angenommen, es gibt keinen praktischen Weg. Es ist nicht dasselbe wie eine Klasse. Tatsächlich ist es eine Schnittstelle (im OOP-Sinne). Es ist nicht schwer zu erkennen, ob ein Funktionspaar mit den gleichen Signaturen austauschbar ist, während zwei Klassen niemals austauschbar sind, selbst wenn sie exakt den gleichen Inhalt haben.
Doval

17

Fabriken haben viele Vorteile, die in manchen Situationen ein elegantes Anwendungsdesign ermöglichen. Zum einen können Sie die Eigenschaften von Objekten, die Sie später erstellen möchten, an einem Ort festlegen, indem Sie eine Factory erstellen und diese Factory dann übergeben. Aber oft muss man das gar nicht machen. In diesem Fall erhöht die Verwendung einer Factory lediglich die Komplexität, ohne dass Sie tatsächlich eine Gegenleistung erhalten. Nehmen wir zum Beispiel diese Fabrik:

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

Eine Alternative zum Factory-Muster ist das sehr ähnliche Builder-Muster. Der Hauptunterschied besteht darin, dass die Eigenschaften der von einer Factory erstellten Objekte beim Initialisieren der Factory festgelegt werden, während ein Builder mit einem Standardstatus initialisiert wird und anschließend alle Eigenschaften festgelegt werden.

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

Wenn Sie jedoch ein Problem mit der Überentwicklung haben, ist das Ersetzen einer Factory durch einen Builder wahrscheinlich keine große Verbesserung.

Der einfachste Ersatz für eines der Muster ist natürlich das Erstellen von Objektinstanzen mit einem einfachen Konstruktor mit dem newOperator:

Widget widget = new ColoredWidget(COLOR_RED);

Konstruktoren haben jedoch in den meisten objektorientierten Sprachen einen entscheidenden Nachteil: Sie müssen ein Objekt dieser genauen Klasse zurückgeben und können keinen Untertyp zurückgeben.

Wenn Sie den Subtyp zur Laufzeit auswählen müssen, aber dafür keine neue Builder- oder Factory-Klasse erstellen möchten, können Sie stattdessen eine Factory-Methode verwenden. Dies ist eine statische Methode einer Klasse, die neue Instanzen dieser Klasse oder einer ihrer Unterklassen zurückgibt. Eine Factory, die keinen internen Zustand beibehält, kann häufig durch eine solche Factory-Methode ersetzt werden:

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

Eine neue Funktion in Java 8 sind Methodenreferenzen, mit denen Sie Methoden weitergeben können, genau wie Sie es mit einer zustandslosen Factory tun würden. Praktischerweise akzeptiert jede Methode, die eine Methodenreferenz akzeptiert, auch alle Objekte, die dieselbe Funktionsschnittstelle implementieren. Dies kann auch eine vollwertige Factory mit internem Status sein, sodass Sie Fabriken später problemlos einführen können, wenn Sie einen Grund dafür sehen.


2
Eine Factory kann häufig auch nach der Erstellung konfiguriert werden. Der Hauptunterschied zu einem Builder besteht darin, dass ein Builder normalerweise zum Erstellen einer einzelnen, komplexen Instanz verwendet wird, während Fabriken zum Erstellen vieler ähnlicher Instanzen verwendet werden.
Kopffüßer

1
danke (+1), aber die Antwort kann nicht erklären, wie andere PLs es lösen würden, trotzdem danke für den Hinweis auf Java 8-Methodenreferenzen.
Senseiwu

1
Ich habe oft gedacht, dass es in einem objektorientierten Framework Sinn macht, die tatsächliche Objektkonstruktion auf den fraglichen Typ zu beschränken und foo = new Bar(23);gleichwertig zu sein foo = Bar._createInstance(23);. Ein Objekt sollte in der Lage sein, seinen eigenen Typ zuverlässig mit einer privaten getRealType()Methode abzufragen , aber Objekte sollten in der Lage sein, einen Supertyp zu bestimmen, der von externen Aufrufen an zurückgegeben werden soll getType(). Externer Code sollte sich nicht darum kümmern, ob ein Aufruf von new String("A")tatsächlich eine Instanz von String[im Gegensatz zu z. B. einer Instanz von SingleCharacterString] zurückgibt .
Supercat

1
Ich verwende viele statische Methodenfabriken, wenn ich eine Unterklasse basierend auf dem Kontext auswählen muss, aber die Unterschiede sollten für den Aufrufer undurchsichtig sein. Zum Beispiel habe ich eine Reihe von Bildklassen, die alle draw(OutputStream out)etwas anderes HTML enthalten, aber generieren. Die statische Methodenfactory erstellt die richtige Klasse für die Situation und dann kann der Aufrufer einfach die Zeichenmethode verwenden.
Michael Shopsin

1
@supercat Vielleicht interessiert Sie ein Blick auf Objective-c. Dort besteht die Konstruktion von Objekten in der Regel aus einfachen Methodenaufrufen [[SomeClass alloc] init]. Es ist möglich, eine völlig andere Klasseninstanz oder ein anderes Objekt (z. B. einen zwischengespeicherten Wert) zurückzugeben
axelarge

3

Fabriken der einen oder anderen Art finden sich unter geeigneten Umständen in so gut wie jeder objektorientierten Sprache. Manchmal müssen Sie einfach nur eine Möglichkeit auswählen, welche Art von Objekt basierend auf einem einfachen Parameter wie einer Zeichenfolge erstellt werden soll.

Einige Leute gehen zu weit und versuchen, ihren Code so zu entwickeln, dass sie nur innerhalb einer Factory einen Konstruktor aufrufen müssen. Es wird langsam lächerlich, wenn man Fabrikfabriken hat.

Was mich beeindruckt hat, als ich Scala lernte und Ruby nicht kenne, ist, dass die Sprache so aussagekräftig ist, dass Programmierer nicht immer versuchen, die "Klempnerarbeit" auf externe Konfigurationsdateien zu übertragen . Anstatt versucht zu sein, Factory-Fabriken zu erstellen, um Ihre Klassen in verschiedenen Konfigurationen miteinander zu verbinden, verwenden Sie in Scala Objekte mit eingemischten Merkmalen. Es ist auch relativ einfach, einfache DSLs in der Sprache für Aufgaben zu erstellen, die in Java häufig stark überarbeitet sind.

Wie auch andere Kommentare und Antworten gezeigt haben, machen Verschlüsse und erstklassige Funktionen viele Muster überflüssig. Aus diesem Grund glaube ich, dass viele der Anti-Patterns mit zunehmender Verbreitung von C ++ 11 und Java 8 verschwinden werden.


danke (+1). Ihre Antwort erklärt einige meiner Zweifel, wie Scala dies vermeiden würde. Glaubst du nicht, dass sobald Scala mindestens halb so populär ist wie Java auf Unternehmensebene, Armeen von Frameworks, Mustern, bezahlten App-Servern usw. auftauchen würden?
Senseiwu

Ich denke, wenn Scala Java überholt, wird es daran liegen, dass die Leute den "Scala-Weg" bevorzugen, um Software zu entwickeln. Was mir als wahrscheinlicher erscheint, ist, dass Java weiterhin mehr der Funktionen einnimmt, die die Benutzer dazu veranlassen, auf Scala zu wechseln, wie Java 5-Generika und Java 8-Lambdas und -Streams, was hoffentlich dazu führen wird, dass Programmierer Anti-Patterns aufgeben, die auftauchen, wenn diese auftauchen Funktionen waren nicht verfügbar. Es wird immer FactoryFactory-Anhänger geben, egal wie gut Sprachen werden. Offensichtlich mögen manche Leute diese Architekturen oder sie wären nicht so verbreitet.
Karl Bielefeldt

2

Im Allgemeinen gibt es diesen Trend, dass Java-Programme fürchterlich überentwickelt sind. Viele Fabriken zu haben, ist eines der häufigsten Symptome von Überentwicklung. Deshalb machen sich die Leute über diese lustig.

Insbesondere besteht das Problem, das Java bei Fabriken hat, darin, dass in Java a) Konstruktoren keine Funktionen sind und b) Funktionen keine erstklassigen Bürger sind.

Stellen Sie sich vor, Sie könnten so etwas schreiben (seien Sie Functioneine bekannte Schnittstelle der 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);

Siehe, keine Factory-Interfaces oder Klassen. Viele dynamische Sprachen haben erstklassige Funktionen. Leider ist dies mit Java 7 oder früher nicht möglich (die Implementierung derselben mit Fabriken bleibt dem Leser als Übung überlassen).

Die Alternative ist also nicht so sehr "ein anderes Muster verwenden", sondern vielmehr "eine Sprache mit einem besseren Objektmodell verwenden" oder "einfache Probleme nicht überentwickeln".


2
dies versucht nicht einmal die gestellte Frage zu beantworten: "Was sind die Alternativen?"
Mücke

1
Lesen Sie nach dem zweiten Absatz den Abschnitt "Wie andere Sprachen dies tun". (PS: die andere Antwort zeigt auch keine Alternativen)
Kopffüßer

4
@gnat Sagt er nicht indirekt, dass die Alternative darin besteht, erstklassige Funktionen zu verwenden? Wenn Sie eine Blackbox wünschen, die Objekte erstellen kann, sind Ihre Optionen entweder eine Fabrik oder eine Funktion, und eine Fabrik ist nur eine verkleidete Funktion.
Doval

3
"In vielen dynamischen Sprachen" ist ein bisschen irreführend, da man eigentlich eine Sprache mit erstklassigen Funktionen braucht. "Dynamisch" ist dazu orthogonal.
Andres F.

Stimmt, aber es ist eine Eigenschaft, die häufig in dynamischen Sprachen vorkommt. Ich erwähnte zu Beginn meiner Antwort die Notwendigkeit einer erstklassigen Funktion.
Kopffüßer
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.