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 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 denkt die Community? Obwohl mir der Singleton-Ansatz nicht gefällt, habe ich anscheinend kein besonders starkes Argument dagegen.
quelle
DateTime
undFile
aus genau den gleichen Gründen bekanntermaßen schwer zu testen. Wenn Sie beispielsweise eine Klasse haben, die dasCreated
DatumDateTime.Now
im 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).private
Konstruktor und einegetInstance()
Methode haben? Entschuldigung, unverbesserlicher Trottel!Antworten:
Warum sollten Sie Ihre Fabrik von dem Objekttyp trennen, den sie erstellt?
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:
Nachteile:
Wirklich, Sie sollten Blochs Buch zu Ihrem Code-Reviewer bringen und sehen, ob Sie beide Blochs Stil zustimmen können.
quelle
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.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:
Was mich neugierig macht, Sie zu fragen:
StringUtils
das richtig entworfen und implementiert ist?quelle
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 derString
Klasse nicht existieren . Etwas so Grundlegendes zu ersetzen, wieString
es nicht in Frage kommt, ist der richtige Weg. (Fortsetzung)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 sichStringUtils
beim Testen nicht lustig - warum sollten sie sich über diese einfache Convenience-Fabrik lustig machen?StringUtils
weil hinreichend sicher ist, dass die Methoden dort keine Nebenwirkungen haben.StringUtils
ein Ergebnis ohne Änderung der Eingaben zurückgegeben wird, ändert meine Fabrik auch nichts.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,SomeFactory
und eine Art Logik schreiben,someCondition
dieSomeFactory
oder betrachtet und aufruftSomeOtherFactory
. 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 machtIFoo
. 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 angimmeAFoo()
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 jedeInstructionsFactory
oderExerciseFactory
eine Kopie des QuestionFactoryMap bekommen. Dann werden die verschiedenen Anleitungen und Übungsfabriken an die übergeben,ContentBlockBuilder
die 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 aufcreateContent()
. 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 einBaseQuestion
statt 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?
quelle
Integer
Klasse nach - einfache Dinge wie das Konvertieren aus einem String. Das ist wasFoo
tut. MeinesFoo
soll 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ührenInteger
, die derzeit über statische Fabriken eingebrannt werden?Foo
ist jedoch viel einfacher als viele der Klassen. Es ist nur ein einfaches POJO.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.Foo
. Zu Recht - ich habe es nie für endgültig erklärt.Foo
ist eine Bohne, die nicht verlängert werden soll.