Statische Fabrik gegen Fabrik als Singleton

24

In einigen meiner Codes habe ich eine statische Factory, die der folgenden ähnelt:

public class SomeFactory {

    // Static class
    private SomeFactory() {...}

    public static Foo createFoo() {...}

    public static Foo createFooerFoo() {...}
}

Während einer Codeüberprüfung wurde vorgeschlagen, dass dies ein Singleton sein und injiziert werden sollte. Also sollte es so aussehen:

public class SomeFactory {

    public SomeFactory() {}

    public Foo createFoo() {...}

    public Foo createFooerFoo() {...}
}

Einige hervorzuhebende Punkte:

  • Beide Fabriken sind staatenlos.
  • Der einzige Unterschied zwischen den Methoden besteht in ihren Gültigkeitsbereichen (Instanz vs. statisch). Die Implementierungen sind die gleichen.
  • Foo ist eine Bohne ohne Schnittstelle.

Die Argumente, die ich hatte, um statisch zu werden, waren:

  • Die Klasse ist zustandslos und muss daher nicht instanziiert werden
  • Es erscheint natürlicher, eine statische Methode aufrufen zu können, als eine Factory instanziieren zu müssen

Die Argumente für die Fabrik als Singleton waren:

  • Es ist gut, alles zu spritzen
  • Trotz der Zustandslosigkeit der Fabrik ist das Testen mit der Injektion einfacher (leicht zu verspotten)
  • Es sollte beim Testen des Verbrauchers verspottet werden

Ich habe einige schwerwiegende Probleme mit dem Singleton-Ansatz, da er darauf hindeutet, dass keine Methode jemals statisch sein sollte. Es scheint auch nahezulegen, dass Dienstprogramme wie StringUtilsverpackt und injiziert werden sollten, was albern erscheint. Schließlich impliziert dies, dass ich die Fabrik irgendwann verspotten muss, was nicht richtig zu sein scheint. Ich kann mir nicht vorstellen, wann ich die Fabrik verspotten muss.

Was denkt die Community? Obwohl mir der Singleton-Ansatz nicht gefällt, habe ich anscheinend kein besonders starkes Argument dagegen.

bstempi
quelle
2
Die Fabrik wird von etwas benutzt . Um dieses Ding für sich zu testen, müssen Sie eine Kopie Ihrer Fabrik bereitstellen. Wenn Sie Ihre Fabrik nicht verspotten, kann ein Fehler dort einen fehlgeschlagenen Komponententest verursachen, wenn dies nicht der Fall sein sollte.
Mike
Befürworten Sie also statische Versorgungsunternehmen im Allgemeinen oder nur statische Fabriken?
bstempi
1
Ich plädiere generell gegen sie. In C # zum Beispiel sind die Klassen DateTimeund Fileaus genau den gleichen Gründen bekanntermaßen schwer zu testen. Wenn Sie beispielsweise eine Klasse haben, die das CreatedDatum DateTime.Nowim Konstruktor festlegt, wie erstellen Sie dann einen Komponententest mit zwei dieser Objekte, die im Abstand von 5 Minuten erstellt wurden? Was ist mit Jahren auseinander? Das kann man wirklich nicht (ohne viel Arbeit).
Mike
2
Sollte Ihre Singleton-Factory nicht einen privateKonstruktor und eine getInstance()Methode haben? Entschuldigung, unverbesserlicher Trottel!
TMN
1
@ Mike - Einverstanden - Ich habe oft eine DateTimeProvider-Klasse verwendet, die einfach DateTime.Now zurückgibt. Sie können es dann in Ihren Tests gegen einen Schein tauschen, der eine festgelegte Zeit zurückgibt. Es wird zusätzliche Arbeit es für alle Konstrukteure vorbei - die größten Kopfschmerzen sind , wenn Mitarbeiter nicht kaufen , um es zu 100% und verrutschen explizite Verweise um DateTime.Now aus Faulheit ... :)
Julia Hayward

Antworten:

15

Warum sollten Sie Ihre Fabrik von dem Objekttyp trennen, den sie erstellt?

public class Foo {

    // Private constructor
    private Foo(...) {...}

    public static Foo of(...) { return new Foo(...); }
}

Dies beschreibt Joshua Bloch als seinen Punkt 1 auf Seite 5 seines Buches "Effektives Java". Java ist ausführlich genug, ohne zusätzliche Klassen und Singletons und so weiter hinzuzufügen. Es gibt einige Fälle, in denen eine separate Factory-Klasse sinnvoll ist, aber nur wenige.

Um Joshua Blochs Punkt 1 zu paraphrasieren, können statische Factory-Methoden im Gegensatz zu Konstruktoren:

  • Namen haben, die bestimmte Arten der Objekterstellung beschreiben können (Bloch verwendet als Beispiel probablePrime (int, int, Random))
  • Rückgabe eines vorhandenen Objekts (zB Fliegengewicht)
  • Gibt einen Untertyp der ursprünglichen Klasse oder einer von ihr implementierten Schnittstelle zurück
  • Reduzieren Sie die Ausführlichkeit (indem Sie Typparameter angeben - siehe Bloch zum Beispiel)

Nachteile:

  • Klassen ohne öffentlichen oder geschützten Konstruktor können nicht in Unterklassen unterteilt werden (Sie können jedoch geschützte Konstruktoren und / oder Element 16 verwenden: Komposition gegenüber Vererbung bevorzugen).
  • Statische Factory-Methoden unterscheiden sich nicht von anderen statischen Methoden (verwenden Sie einen Standardnamen wie () oder valueOf ()).

Wirklich, Sie sollten Blochs Buch zu Ihrem Code-Reviewer bringen und sehen, ob Sie beide Blochs Stil zustimmen können.

GlenPeterson
quelle
Ich habe auch darüber nachgedacht und ich denke, es gefällt mir. Ich kann mich nicht erinnern, warum ich sie überhaupt getrennt habe.
bstempi
Generell sind wir uns über Blochs Stil einig. Letztendlich werden wir unsere Factory-Methoden in die Klasse verschieben, die sie instanziieren. Der einzige Unterschied zwischen unserer und Ihrer Lösung besteht darin, dass der Konstruktor öffentlich bleibt, sodass die Klasse weiterhin direkt instanziiert werden kann. Danke für deine Antwort!
bstempi
Aber ... das ist schrecklich. Sie möchten höchstwahrscheinlich eine Klasse, die IFoo implementiert und übergeben hat. Mit diesem Muster ist dies nicht mehr möglich. Sie müssen IFoo fest codieren, um Foo zu bedeuten, um Instanzen zu erhalten. Wenn Sie IFooFactory haben, die ein IFoo create () enthält, muss der Client-Code nicht die Implementierungsdetails kennen, für die Foo als IFoo ausgewählt wurde.
Wilbert
@Wilbert public static IFoo of(...) { return new Foo(...); } Was ist das Problem? Bloch-Listen, die Ihr Anliegen befriedigen, sind einer der Vorteile dieses Entwurfs (zweiter Punkt oben). Ich muss Ihren Kommentar nicht verstehen.
GlenPeterson
Für das Erstellen einer Instanz wird Folgendes erwartet: Foo.of (...). Die Existenz von Foo sollte jedoch niemals aufgedeckt werden, nur IFoo sollte bekannt sein (da Foo nur ein konkretes Werkzeug von IFoo ist). Eine statische Factory-Methode auf dem konkreten Gerät unterbricht dies und führt zu einer Kopplung zwischen dem Client-Code und der konkreten Implementierung.
Wilbert
5

Hier sind einige Probleme mit der Statik in Java:

  • statische Methoden werden von den OOP-"Regeln" nicht berücksichtigt. Sie können eine Klasse nicht zwingen, bestimmte statische Methoden zu implementieren (soweit ich weiß). Sie können also nicht mehrere Klassen haben, die denselben Satz statischer Methoden mit denselben Signaturen implementieren. Damit Sie stecken sind ein von was auch immer es ist , du tust. Sie können eine statische Klasse auch nicht wirklich in Unterklassen unterteilen. Also kein Polymorphismus etc.

  • Da Methoden keine Objekte sind, können statische Methoden nicht weitergegeben und nicht verwendet werden (ohne jede Menge Boilerplate-Codierung), außer durch Code, der fest mit ihnen verknüpft ist - was den Client-Code stark macht gekoppelt. (Um fair zu sein, ist dies nicht nur die Schuld der Statik).

  • statische Felder sind Globalen ziemlich ähnlich


Du erwähntest:

Ich habe einige schwerwiegende Probleme mit dem Singleton-Ansatz, da er darauf hindeutet, dass keine Methode jemals statisch sein sollte. Es scheint auch nahezulegen, dass Dienstprogramme wie StringUtils verpackt und injiziert werden sollten, was albern erscheint. Schließlich impliziert dies, dass ich die Fabrik irgendwann verspotten muss, was nicht richtig zu sein scheint. Ich kann mir nicht vorstellen, wann ich die Fabrik verspotten muss.

Was mich neugierig macht, Sie zu fragen:

  • Was sind Ihrer Meinung nach die Vorteile statischer Methoden?
  • Glauben Sie, dass StringUtilsdas richtig entworfen und implementiert ist?
  • warum denkst du, musst du dich nie über die Fabrik lustig machen?

quelle
Hey, danke für die Antwort! In der Reihenfolge, in der Sie gefragt haben: (1) Die Vorteile gegenüber statischen Methoden sind syntaktischer Zucker und das Fehlen einer unnötigen Instanz. Es ist schön, Code zu lesen, der einen statischen Import hat, und etwas zu sagen, Foo f = createFoo();anstatt eine benannte Instanz zu haben. Die Instanz selbst bietet kein Dienstprogramm. (2) Ich denke schon. Es wurde entwickelt, um die Tatsache zu überwinden, dass viele dieser Methoden in der StringKlasse nicht existieren . Etwas so Grundlegendes zu ersetzen, wie Stringes nicht in Frage kommt, ist der richtige Weg. (Fortsetzung)
bstempi
Sie sind einfach zu bedienen und erfordern keine Instanzen. Sie fühlen sich natürlich an. (3) Weil meine Fabrik den Fabrikmethoden innerhalb sehr ähnlich ist Integer. Sie sind sehr einfach, haben sehr wenig Logik und dienen nur der Bequemlichkeit (z. B. gibt es keinen anderen OO-Grund, sie zu haben). Der Hauptteil meiner Argumentation ist, dass sie sich auf keinen Staat verlassen - es gibt keinen Grund, sie während der Tests zu isolieren. Die Leute machen sich StringUtilsbeim Testen nicht lustig - warum sollten sie sich über diese einfache Convenience-Fabrik lustig machen?
bstempi
3
Sie verspotten nicht, StringUtilsweil hinreichend sicher ist, dass die Methoden dort keine Nebenwirkungen haben.
Robert Harvey
@RobertHarvey Ich nehme an, ich mache den gleichen Anspruch auf meine Fabrik - sie ändert nichts. Genau wie StringUtilsein Ergebnis ohne Änderung der Eingaben zurückgegeben wird, ändert meine Fabrik auch nichts.
bstempi
@bstempi Ich habe mich dort für ein bisschen sokratische Methode entschieden. Ich schätze ich sauge dran.
0

Ich denke, die Anwendung, die Sie erstellen, muss ziemlich unkompliziert sein, sonst befinden Sie sich relativ früh im Lebenszyklus. Das einzige andere Szenario, in dem ich mir vorstellen kann, dass Sie noch keine Probleme mit Ihrer Statik gehabt hätten, ist, wenn Sie es geschafft haben, die anderen Teile Ihres Codes, die über Ihre Factory Bescheid wissen, auf ein oder zwei Stellen zu beschränken.

Stellen Sie sich vor, Ihre Anwendung entwickelt sich und in einigen Fällen müssen Sie eine FooBar(Unterklasse von Foo) erstellen . Jetzt müssen Sie alle Stellen in Ihrem Code durchgehen, die über Ihre Daten Bescheid wissen, SomeFactoryund eine Art Logik schreiben, someConditiondie SomeFactoryoder betrachtet und aufruft SomeOtherFactory. Wenn Sie sich Ihren Beispielcode ansehen, ist er tatsächlich schlimmer. Sie wollen nur Methode für Methode hinzufügen, um verschiedene Foos zu erstellen, und Ihr gesamter Client-Code muss eine Bedingung prüfen, bevor Sie herausfinden, welche Foo erstellt werden soll. Das bedeutet, dass alle Kunden genau wissen müssen, wie Ihre Factory funktioniert, und dass sie sich alle ändern müssen, wenn ein neuer Foo benötigt (oder entfernt wird!).

Nun stellen Sie sich vor, Sie hätten gerade eine erstellt IFooFactory, die eine macht IFoo. Jetzt ist es ein Kinderspiel, eine andere Unterklasse oder sogar eine völlig andere Implementierung zu erstellen - Sie geben einfach die richtige IFooFactory weiter oben in die Kette ein. Wenn der Kunde sie erhält, ruft er an gimmeAFoo()und erhält die richtige foo, weil er die richtige Factory hat .

Sie fragen sich möglicherweise, wie sich dies im Produktionscode auswirkt. Um Ihnen ein Beispiel aus der Codebasis zu geben, an der ich gerade arbeite und die nach etwas mehr als einem Jahr der Projektausweitung allmählich abläuft, habe ich eine Reihe von Fabriken, die zum Erstellen von Fragendatenobjekten verwendet werden (sie ähneln Präsentationsmodellen) ). Ich habe eine QuestionFactoryMap, die im Grunde genommen ein Hash ist, um nachzuschlagen, welche Fragenfabrik wann verwendet werden soll. Um eine Anwendung zu erstellen, die verschiedene Fragen stellt, habe ich verschiedene Fabriken in die Karte eingefügt.

Fragen können entweder in Anweisungen oder Übungen vorgestellt (die beide implementieren IContentBlock), so dass jede InstructionsFactoryoder ExerciseFactoryeine Kopie des QuestionFactoryMap bekommen. Dann werden die verschiedenen Anleitungen und Übungsfabriken an die übergeben, ContentBlockBuilderdie wiederum einen Hash verwaltet, von dem sie wann verwenden können. Wenn das XML geladen wird, ruft der ContentBlockBuilder die Exercise- oder Instructions-Factory aus dem Hash auf und fordert sie dazu auf createContent(). Anschließend delegiert die Factory die Erstellung der Fragen an das, was sie aus ihrer internen QuestionFactoryMap herausholt ist in jedem XML-Knoten gegen den Hash. Leider ist das Ding ein BaseQuestionstatt einIQuestion, aber das zeigt nur, dass Sie selbst dann Fehler machen können, wenn Sie beim Design vorsichtig sind, die Sie auf der ganzen Linie verfolgen können.

Dein Bauch sagt dir, dass diese Statik dir wehtun wird, und es gibt sicherlich viel in der Literatur, was deinen Bauch stützt. Sie werden niemals verlieren, wenn Sie Dependency Injection haben, das Sie nur für Ihre Komponententests verwenden (nicht, wenn Sie guten Code erstellen, werden Sie es meiner Meinung nach nicht mehr verwenden), aber die Statik kann Ihre Fähigkeit vollständig zerstören um schnell und einfach Ihren Code zu ändern. Warum also überhaupt dorthin gehen?

Amy Blankenship
quelle
Denken Sie einen Moment über die Factory-Methoden in der IntegerKlasse nach - einfache Dinge wie das Konvertieren aus einem String. Das ist was Footut. Meines Foosoll nicht verlängert werden (meine Schuld dafür, dass ich das nicht angegeben habe), also habe ich keine polymorphen Probleme. Ich verstehe den Wert von polymorphen Fabriken und habe sie in der Vergangenheit benutzt ... Ich denke einfach nicht, dass sie hier angebracht sind. Können Sie sich vorstellen, dass Sie eine Fabrik instanziieren müssen, um die einfachen Operationen auszuführen Integer, die derzeit über statische Fabriken eingebrannt werden?
bstempi
Außerdem stimmen Ihre Annahmen bezüglich der Anwendung nicht ganz. Die Anwendung ist ziemlich aufwendig. Dies Fooist jedoch viel einfacher als viele der Klassen. Es ist nur ein einfaches POJO.
bstempi
Wenn Foo erweitert werden soll, dann gibt es einen Grund dafür, dass die Factory eine Instanz ist - Sie geben die richtige Instanz der Factory an, um die richtige Unterklasse zu erhalten, anstatt den if (this) then {makeThisFoo()} else {makeThatFoo()}gesamten Code aufzurufen . Eine Sache, die ich bei Menschen mit chronisch schlechter Architektur bemerkt habe, ist, dass sie wie Menschen mit chronischen Schmerzen sind. Sie wissen eigentlich nicht, wie es sich anfühlt, keine Schmerzen zu haben, also scheinen die Schmerzen, die sie haben, gar nicht so schlimm zu sein.
Amy Blankenship
Vielleicht möchten Sie auch diesen Link über die Probleme mit statischen Methoden (auch die mathematischen!) Überprüfen
Amy Blankenship
Ich habe die Frage aktualisiert. Sie und eine andere Person erwähnten das Erweitern Foo. Zu Recht - ich habe es nie für endgültig erklärt. Fooist eine Bohne, die nicht verlängert werden soll.
bstempi