In unserer Java-Codebasis sehe ich immer wieder folgendes Muster:
/**
This is a stateless utility class
that groups useful foo-related operations, often with side effects.
*/
public class FooUtil {
public int foo(...) {...}
public void bar(...) {...}
}
/**
This class does applied foo-related things.
*/
class FooSomething {
int DoBusinessWithFoo(FooUtil fooUtil, ...) {
if (fooUtil.foo(...)) fooUtil.bar(...);
}
}
Was mich stört ist, dass ich von FooUtil
überall her eine Instanz bestehen muss , um zu testen.
- Ich kann die
FooUtil
Methoden nicht statisch machen, weil ich sie nicht zum Testen der beidenFooUtil
Klassen und ihrer Clientklassen verspotten kann . - Ich kann mit a keine Instanz von
FooUtil
am Ort des Verbrauchs erstellennew
, da ich sie zum Testen nicht verspotten kann.
Ich nehme an, meine beste Wette ist die Injektion (und das tue ich), aber es fügt seine eigenen Probleme hinzu. Wenn Sie mehrere util-Instanzen übergeben, wird auch die Größe der Methodenparameterlisten erhöht.
Gibt es eine Möglichkeit, besser damit umzugehen, die ich nicht sehe?
Update: Da die Utility-Klassen zustandslos sind, könnte ich wahrscheinlich ein statisches Singleton- INSTANCE
Member oder eine statische getInstance()
Methode hinzufügen , während die Möglichkeit erhalten bleibt, das zugrunde liegende statische Feld der Klasse zum Testen zu aktualisieren. Scheint auch nicht super sauber zu sein.
FooUtil
Methoden eingefügt werdenFooSomething
, vielleicht gibt es EigenschaftenFooSomething
, die geändert / erweitert werden sollten, damit dieFooUtil
Methoden nicht mehr benötigt werden. Bitte geben Sie uns ein konkreteres Beispiel dafür, wasFooSomething
uns bei der Beantwortung dieser Fragen helfen kann.Antworten:
Lassen Sie mich zunächst sagen, dass es unterschiedliche Programmieransätze für unterschiedliche Probleme gibt. Für meine Arbeit bevorzuge ich ein starkes OO-Design für Organisation und Wartung, und meine Antwort spiegelt dies wider. Ein funktionaler Ansatz hätte eine ganz andere Antwort.
Ich finde, dass die meisten Utility-Klassen erstellt werden, weil das Objekt, auf dem sich die Methoden befinden sollten, nicht vorhanden ist. Dies kommt fast immer aus einem von zwei Fällen: Entweder gibt jemand Sammlungen weiter oder jemand gibt Bohnen weiter.
Wenn Sie eine Sammlung haben, die Sie weitergeben, muss es Code geben, der Teil dieser Sammlung sein soll. Dies wird dann im gesamten System verteilt und schließlich in Dienstprogrammklassen zusammengefasst.
Mein Vorschlag ist, Ihre Sammlungen (oder Beans) mit anderen eng verknüpften Daten in neue Klassen zu verpacken und den "Utility" -Code in tatsächliche Objekte einzufügen.
Sie könnten die Auflistung erweitern und die Methoden dort hinzufügen, verlieren aber auf diese Weise die Kontrolle über den Status Ihres Objekts. Ich schlage vor, Sie kapseln ihn vollständig und machen nur die erforderlichen Geschäftslogikmethoden verfügbar (denken Sie: "Fragen Sie Ihre Objekte nicht nach ihren Daten." Geben Sie Ihren Objekten Befehle und lassen Sie sie auf ihre privaten Daten einwirken ", ein wichtiger OO-Mandant, der, wenn Sie darüber nachdenken, den hier vorgeschlagenen Ansatz erfordert.
Dieses "Umschließen" ist nützlich für allgemeine Bibliotheksobjekte, die Daten enthalten, denen Sie jedoch keinen Code hinzufügen können. Das Einfügen von Code in die von ihm bearbeiteten Daten ist ein weiteres wichtiges Konzept im OO-Entwurf.
Es gibt viele Codierungsstile, bei denen dieses Umbruchkonzept eine schlechte Idee wäre - wer möchte eine ganze Klasse schreiben, wenn er nur ein einmaliges Skript oder einen Test implementiert? Ich bin jedoch der Meinung, dass die Kapselung von Basistypen / Sammlungen, denen Sie keinen Code hinzufügen können, für OO obligatorisch ist.
quelle
Sie können ein Provider-Muster verwenden und
FooSomething
einFooUtilProvider
Objekt in Ihr einfügen (möglicherweise in einem Konstruktor; nur einmal).FooUtilProvider
wäre nur eine Methode haben:FooUtils get();
. Die Klasse würde dann die von ihr bereitgestellteFooUtils
Instanz verwenden.Dies ist nur ein Schritt von der Verwendung eines Abhängigkeitsinjektions-Frameworks entfernt, wenn Sie DI-Cointainer nur noch zweimal verbinden müssen: für Produktionscode und für Ihre Testsuite. Sie binden die
FooUtils
Schnittstelle einfach anRealFooUtils
oderMockedFooUtils
, und der Rest geschieht automatisch. Jedes Objekt, das davon abhängt,FooUtilsProvider
erhält die richtige Version vonFooUtils
.quelle