Wir haben ziemlich viele Stellen im Quellcode unserer Anwendung, an denen eine Klasse viele Methoden mit gleichen Namen und unterschiedlichen Parametern hat. Diese Methoden haben immer alle Parameter einer 'vorherigen' Methode plus einen weiteren.
Es ist ein Ergebnis der langen Evolution (Legacy-Code) und dieses Denkens (ich glaube):
" Es gibt eine Methode M, die A ausführt. Ich muss A + B ausführen. OK, ich weiß ... Ich werde M einen neuen Parameter hinzufügen, eine neue Methode dafür erstellen, Code von M auf die neue Methode verschieben mit einem weiteren Parameter machen Sie dort das A + B und rufen die neue Methode von M mit einem Standardwert des neuen Parameters auf. "
Hier ist ein Beispiel (in Java-ähnlicher Sprache):
class DocumentHome {
(...)
public Document createDocument(String name) {
// just calls another method with default value of its parameter
return createDocument(name, -1);
}
public Document createDocument(String name, int minPagesCount) {
// just calls another method with default value of its parameter
return createDocument(name, minPagesCount, false);
}
public Document createDocument(String name, int minPagesCount, boolean firstPageBlank) {
// just calls another method with default value of its parameter
return createDocument(name, minPagesCount, false, "");
}
public Document createDocument(String name, int minPagesCount, boolean firstPageBlank, String title) {
// here the real work gets done
(...)
}
(...)
}
Ich denke, das ist falsch. Nicht nur, dass wir nicht für immer neue Parameter wie diese hinzufügen können, sondern auch, dass der Code aufgrund aller Abhängigkeiten zwischen den Methoden schwer zu erweitern / zu ändern ist.
Hier sind einige Möglichkeiten, wie Sie dies besser machen können:
Führen Sie ein Parameterobjekt ein:
class DocumentCreationParams { String name; int minPagesCount; boolean firstPageBlank; String title; (...) } class DokumentHome { public Document createDocument(DocumentCreationParams p) { // here the real work gets done (...) } }
Stellen Sie die Parameter auf das
DocumentHome
Objekt ein, bevor Sie es aufrufencreateDocument()
@In DocumentHome dh = null; (...) dh.setName(...); dh.setMinPagesCount(...); dh.setFirstPageBlank(...); Document newDocument = dh.createDocument();
Teilen Sie die Arbeit in verschiedene Methoden auf und rufen Sie sie nach Bedarf auf:
@In DocumentHome dh = null; Document newDocument = dh.createDocument(); dh.changeName(newDocument, "name"); dh.addFirstBlankPage(newDocument); dh.changeMinPagesCount(new Document, 10);
Meine Fragen:
- Ist das beschriebene Problem wirklich ein Problem?
- Was halten Sie von Lösungsvorschlägen? Welches würdest du bevorzugen (basierend auf deinen Erfahrungen)?
- Können Sie sich eine andere Lösung vorstellen?
Antworten:
Vielleicht versuchen Sie das Builder-Muster ? (Anmerkung: ziemlich zufälliges Google-Ergebnis :)
Ich kann keinen vollständigen Überblick darüber geben, warum ich Builder gegenüber den von Ihnen angegebenen Optionen bevorzuge, aber Sie haben ein großes Problem mit viel Code festgestellt. Wenn Sie der Meinung sind, dass Sie mehr als zwei Parameter für eine Methode benötigen, ist Ihr Code wahrscheinlich falsch strukturiert (und einige würden einen argumentieren!).
Das Problem mit einem params-Objekt ist (es sei denn, das Objekt, das Sie erstellen, ist in irgendeiner Weise real), dass Sie das Problem nur um eine Stufe nach oben schieben und am Ende eine Gruppe von nicht verwandten Parametern erhalten, die das 'Objekt' bilden.
Ihre anderen Versuche sehen für mich so aus, als würde jemand nach dem Baumuster greifen, aber nicht ganz dahin kommen :)
quelle
DocumentoFactory
). Wenn Sie einebuilder
an verschiedenen Stellen haben, können Sie die zukünftigen Änderungen an der Dokumentkonstruktion nur schwer kontrollieren (z. B. ein neues obligatorisches Feld zum Dokument hinzufügen) und zusätzlichen Code für Tests hinzufügen, um die Anforderungen des Document Builders für Klassen zu erfüllen, die Builder verwenden.Die Verwendung eines Parameterobjekts ist ein guter Weg, um eine (übermäßige) Überladung von Methoden zu vermeiden:
Ich würde jedoch nicht zu weit gehen.
Eine Überlastung hier und da zu haben, ist keine schlechte Sache. Es wird von der Programmiersprache unterstützt, nutzen Sie es also zu Ihrem Vorteil.
Ich kannte das Baumuster nicht, habe es aber "zufällig" bei einigen Gelegenheiten verwendet. Gleiches gilt hier: übertreiben Sie es nicht. Der Code in Ihrem Beispiel würde davon profitieren, aber es ist nicht sehr effizient, viel Zeit damit zu verbringen, ihn für jede Methode mit einer einzelnen Überladungsmethode zu implementieren.
Nur meine 2 Cent.
quelle
Ich sehe ehrlich gesagt kein großes Problem mit dem Code. In C # und C ++ können Sie optionale Parameter verwenden, das wäre eine Alternative, aber soweit ich weiß, unterstützt Java diese Art von Parametern nicht.
In C # können Sie alle Überladungen als privat kennzeichnen, und eine Methode mit optionalen Parametern ist public, um das Zeug aufzurufen.
Um Ihre Frage Teil 2 zu beantworten, würde ich das Parameterobjekt oder sogar ein Wörterbuch / HashMap nehmen.
Wie so:
Als Haftungsausschluss bin ich zuerst ein C # - und JavaScript-Programmierer und dann ein Java-Programmierer.
quelle
Ich denke, das ist ein guter Kandidat für das Baumuster. Das Builder-Muster ist nützlich, wenn Sie Objekte desselben Typs mit unterschiedlichen Darstellungen erstellen möchten.
In Ihrem Fall hätte ich einen Builder mit den folgenden Methoden:
Sie können den Builder dann folgendermaßen verwenden:
Nebenbei bemerkt, ich habe nichts gegen ein paar einfache Überladungen - was zum Teufel, .NET Framework verwendet sie überall mit HTML-Helfern. Ich würde jedoch neu bewerten, was ich tue, wenn ich mehr als zwei Parameter an jede Methode übergeben muss.
quelle
Ich denke, dass die
builder
Lösung in den meisten Szenarien funktionieren kann, aber in komplexeren Fällen ist Ihr Builder auch kompliziert einzurichten , da es leicht ist, Fehler in der Reihenfolge der Methoden zu begehen, welche Methoden verfügbar gemacht werden müssen oder nicht usw. Viele von uns bevorzugen daher eine einfachste Lösung.Wenn Sie nur einen einfachen Builder zum Erstellen eines Dokuments erstellen und diesen Code auf verschiedene Teile (Klassen) der Anwendung verteilen, ist Folgendes schwierig:
Dies beantwortet jedoch nicht die OP-Frage.
Die Alternative zur Überlastung
Einige Alternativen:
createDocument
, wie:createLicenseDriveDocument
,createDocumentWithOptionalFields
etc. Natürlich, das Sie zu riesigen Methodennamen führen kann, so dass dies nicht der Fall ist Eine Lösung für alle Fälle.Document
, zDocument.createLicenseDriveDocument()
.createDocument(InterfaceDocument interfaceDocument)
erstellen und verschiedene Implementierungen für erstellenInterfaceDocument
. Per Beispiel:createDocument(new DocumentMinPagesCount("name"))
. Natürlich benötigen Sie nicht für jeden Fall eine einzige Implementierung, da Sie für jede Implementierung mehrere Konstruktoren erstellen und einige Felder gruppieren können, die für diese Implementierung sinnvoll sind. Dieses Muster nennt man Teleskopkonstruktoren .Oder bleiben Sie einfach bei der Überlastlösung . Auch wenn es sich manchmal um eine hässliche Lösung handelt, gibt es nicht viele Nachteile bei der Verwendung. In diesem Fall bevorzuge ich die Verwendung von Überladungsmethoden für eine getrennte Klasse, z. B. eine
DocumentoFactory
, die als Abhängigkeit von Klassen eingefügt werden kann, die Dokumente erstellen müssen. Ich kann die Felder organisieren und validieren, ohne einen guten Builder erstellen zu müssen, und den Code auch an einer einzigen Stelle verwalten.quelle