Nehmen wir an, ich habe eine Prozedur, die Sachen macht :
void doStuff(initalParams) {
...
}
Jetzt entdecke ich, dass "Sachen machen" eine ziemlich komplizierte Operation ist. Die Prozedur wird groß, ich teile sie in mehrere kleinere Prozeduren auf und bald merke ich, dass es nützlich wäre, wenn ich einen Zustand habe , damit ich weniger Parameter zwischen den kleinen Prozeduren übergeben muss. Also zerlege ich es in eine eigene Klasse:
class StuffDoer {
private someInternalState;
public Start(initalParams) {
...
}
// some private helper procedures here
...
}
Und dann nenne ich es so:
new StuffDoer().Start(initialParams);
oder so:
new StuffDoer(initialParams).Start();
Und das fühlt sich falsch an. Wenn ich die .NET- oder Java-API verwende, rufe ich nie auf new SomeApiClass().Start(...);
, was den Verdacht aufwirft, dass ich es falsch mache. Klar, ich könnte den Konstruktor von StuffDoer privat machen und eine statische Hilfsmethode hinzufügen:
public static DoStuff(initalParams) {
new StuffDoer().Start(initialParams);
}
Aber dann hätte ich eine Klasse, deren externe Schnittstelle nur aus einer statischen Methode besteht, was sich auch komisch anfühlt.
Daher meine Frage: Gibt es ein gut etabliertes Muster für diese Art von Klassen, dass
- habe nur einen Einstiegspunkt und
- keinen "extern erkennbaren" Zustand haben, dh der Instanzzustand wird nur während der Ausführung dieses einen Eintrittspunktes benötigt?
bool arrayContainsSomestring = new List<string>(stringArray).Contains("somestring");
bei denen es mir nur darum ging, dass bestimmte Informationen und die LINQ-Erweiterungsmethoden nicht verfügbar sind. Funktioniert einwandfrei und passt in eineif()
Situation, in der Sie nicht durch Reifen springen müssen. Natürlich möchten Sie eine müllsammelnde Sprache, wenn Sie Code wie diesen schreiben.Antworten:
Es gibt ein Muster namens Method Object, bei dem Sie eine einzelne große Methode mit vielen temporären Variablen / Argumenten in eine separate Klasse zerlegen. Sie tun dies, anstatt nur Teile der Methode in separate Methoden zu extrahieren, da diese Zugriff auf den lokalen Status (die Parameter und temporären Variablen) benötigen, und Sie können den lokalen Status nicht mithilfe von Instanzvariablen gemeinsam nutzen, da sie für diese Methode lokal wären (und die daraus extrahierten Methoden) und würden vom Rest des Objekts nicht verwendet.
Stattdessen wird die Methode zu einer eigenen Klasse, die Parameter und temporären Variablen werden zu Instanzvariablen dieser neuen Klasse, und dann wird die Methode in kleinere Methoden der neuen Klasse aufgeteilt. Die resultierende Klasse verfügt normalerweise nur über eine einzige öffentliche Instanzmethode, die die von der Klasse gekapselte Aufgabe ausführt.
quelle
Ich würde sagen, dass die betreffende Klasse (unter Verwendung eines einzelnen Einstiegspunkts) gut gestaltet ist. Es ist einfach zu bedienen und zu erweitern. Ganz SOLID.
Gleiches für
no "externally recognizable" state
. Es ist eine gute Verkapselung.Ich sehe kein Problem mit Code wie:
quelle
doer
erneut verwenden müssen, reduziert sich dies aufvar result = new StuffDoer(initialParams).Calculate(extraParams);
.StringComparer
Klasse, die du verwendest,new StringComparer().Compare( "bleh", "blah" )
gut gestaltet? Was ist mit der Schaffung eines Staates, wenn das überhaupt nicht nötig ist? Okay, das Verhalten ist gut gekapselt (zumindest für den Benutzer der Klasse, mehr dazu in meiner Antwort ), aber das macht es nicht standardmäßig zu 'gutem Design'. Als Beispiel für Ihr "Macher-Ergebnis". Dies würde nur Sinn machen, wenn Sie jemalsdoer
getrennt von verwenden würdenresult
.one reason to change
. Der Unterschied mag subtil erscheinen. Sie müssen verstehen, was das bedeutet. Es wird niemals eine Methodenklasse erstellenAus der SOLID Prinzipien Perspektive Antwort jgauffin die Sinn macht. Vergessen Sie jedoch nicht die allgemeinen Gestaltungsprinzipien, z . B. das Ausblenden von Informationen .
Ich sehe mehrere Probleme mit dem angegebenen Ansatz:
Einige verwandte Referenzen
Möglicherweise liegt ein Hauptargument darin, wie weit das Prinzip der einheitlichen Verantwortung gedehnt werden kann . "Wenn Sie es so extrem nehmen und Klassen bauen, die einen Grund haben zu existieren, haben Sie möglicherweise nur eine Methode pro Klasse. Dies würde eine große Zahl von Klassen verursachen, selbst für die einfachsten Prozesse, was dazu führen würde, dass das System existiert schwer zu verstehen und schwer zu ändern. "
Ein weiterer relevanter Verweis zu diesem Thema: "Unterteilen Sie Ihre Programme in Methoden, die eine identifizierbare Aufgabe ausführen. Halten Sie alle Operationen in einer Methode auf derselben Abstraktionsebene ." - Kent Beck Key ist hier "dieselbe Abstraktionsebene". Das bedeutet nicht "eins", wie es oft interpretiert wird. Diese Abstraktionsebene hängt vollständig von dem Kontext ab, für den Sie entwerfen.
Was ist der richtige Ansatz?
Ohne Ihren konkreten Anwendungsfall zu kennen, ist es schwer zu sagen. Es gibt ein Szenario, in dem ich manchmal (nicht oft) einen ähnlichen Ansatz verwende. Wenn ich einen Datensatz verarbeiten möchte, ohne diese Funktionalität für den gesamten Klassenbereich verfügbar machen zu wollen. Ich habe einen Blog-Beitrag darüber geschrieben, wie Lambdas die Kapselung noch weiter verbessern können . Ich habe auch eine Frage zum Thema hier auf Programmierer gestartet . Das Folgende ist ein aktuelles Beispiel für die Verwendung dieser Technik.
Da der örtliche Code im
ForEach
nirgendwo anders wiederverwendet wird, kann ich ihn einfach genau dort aufbewahren, wo er relevant ist. Code so zu skizzieren, dass aufeinander aufbauender Code stark gruppiert ist, macht ihn meiner Meinung nach besser lesbar.Mögliche Alternativen
quelle
Ich würde sagen, es ist ziemlich gut, aber Ihre API sollte immer noch eine statische Methode für eine statische Klasse sein, da der Benutzer dies erwartet. Die Tatsache, dass die statische Methode verwendet wird
new
, um Ihr Hilfsobjekt zu erstellen und die Arbeit zu erledigen, ist ein Implementierungsdetail, das vor jedem, der es aufruft, verborgen werden sollte.quelle
Es gibt ein Problem mit ahnungslosen Entwicklern, die eine gemeinsam genutzte Instanz verwenden und verwenden könnten
Hierfür ist eine explizite Dokumentation erforderlich, die besagt, dass es nicht threadsicher ist. Wenn es kompakt ist (schnell erstellt, weder externe Ressourcen noch viel Speicher belegt), ist es in Ordnung, es im laufenden Betrieb zu instanziieren.
Das Ausblenden in einer statischen Methode hilft dabei, da die Instanz dann nie wiederverwendet wird.
Wenn es einige teure Initialisierung hat, es könnte (es ist nicht immer) von Vorteil sein, bereiten Sie es einmal initialisiert und es mehrmals unter Verwendung von einer anderen Klasse zu schaffen, Staat nur enthalten würde und es klonbar machen. Durch die Initialisierung würde ein Status erstellt, in dem gespeichert wird
doer
.start()
würde es klonen und an interne Methoden übergeben.Dies würde auch andere Dinge wie den anhaltenden Zustand der teilweisen Ausführung ermöglichen. (Wenn es lange dauert und externe Faktoren, z. B. Stromausfall, die Ausführung unterbrechen könnten.) Aber diese zusätzlichen ausgefallenen Dinge werden normalerweise nicht benötigt, sind es also nicht wert.
quelle
Abgesehen von meiner ausführlicheren, persönlicheren Antwort habe ich das Gefühl, dass die folgende Bemerkung eine separate Antwort verdient.
Es gibt Hinweise darauf, dass Sie dem Anti-Muster der Poltergeisten folgen .
quelle
Martin Fowlers P of EAA-Katalog enthält mehrere Muster .
quelle