Wie vermeide ich eine übermäßige Methodenüberladung?

16

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:

  1. 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
        (...)
      }
    }
    
  2. Stellen Sie die Parameter auf das DocumentHomeObjekt ein, bevor Sie es aufrufencreateDocument()

      @In
      DocumentHome dh = null;
    
      (...)
    
      dh.setName(...);
      dh.setMinPagesCount(...);
      dh.setFirstPageBlank(...);
    
      Document newDocument = dh.createDocument();
    
  3. 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:

  1. Ist das beschriebene Problem wirklich ein Problem?
  2. Was halten Sie von Lösungsvorschlägen? Welches würdest du bevorzugen (basierend auf deinen Erfahrungen)?
  3. Können Sie sich eine andere Lösung vorstellen?
Ytus
quelle
1
Auf welche Sprache zielen Sie ab oder handelt es sich nur um etwas Allgemeines?
Knerd
Keine bestimmte Sprache, nur allgemein. Fühlen Sie sich frei zu zeigen, wie Funktionen in anderen Sprachen dabei helfen können.
Ytus
Wie ich hier sagte, programmers.stackexchange.com/questions/235096/… C # und C ++ haben einige Funktionen.
Knerd
Es ist ziemlich klar, dass diese Frage für jede Sprache gilt, die eine solche Methodenüberladung unterstützt.
Doc Brown
1
@ DocBrown ok, aber nicht jede Sprache unterstützt die gleichen Alternativen;)
Knerd

Antworten:

20

Vielleicht versuchen Sie das Builder-Muster ? (Anmerkung: ziemlich zufälliges Google-Ergebnis :)

var document = new DocumentBuilder()
                   .FirstPageBlank()
                   .Name("doc1final(2).doc")
                   .MinimumNumberOfPages(4)
                   .Build();

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 :)

Froome
quelle
Vielen Dank. Ihre Antwort kann Lösungsnummer sein. 4, ja. Ich suche mehr nach Antworten auf diese Weise: "Nach meiner Erfahrung führt dies zu ... und kann behoben werden ...." oder 'Wenn ich das im Code meines Kollegen sehe, schlage ich ihn stattdessen ... vor.'
Ytus
Ich weiß nicht ... scheint mir, dass Sie nur das Problem bewegen. Wenn ich beispielsweise ein Dokument in verschiedenen Teilen der Anwendung erstellen kann, ist es besser, dies zu organisieren und zu testen und diese Dokumentkonstruktion in einer getrennten Klasse zu isolieren (wie bei Verwendung von a DocumentoFactory). Wenn Sie eine builderan 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.
Dherik
1

Die Verwendung eines Parameterobjekts ist ein guter Weg, um eine (übermäßige) Überladung von Methoden zu vermeiden:

  • es räumt den Code auf
  • trennt die Daten von der Funktionalität
  • macht den Code wartbarer

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.

rauben
quelle
0

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:

class DokumentHome {

  public Document createDocument(Map<string, object> params) {
    if (params.containsKey("yourkey")) {
       // do that
    }
    // here the real work gets done
    (...)
  }
}

Als Haftungsausschluss bin ich zuerst ein C # - und JavaScript-Programmierer und dann ein Java-Programmierer.

Knerd
quelle
4
Es ist eine Lösung, aber ich denke nicht, dass es eine gute Lösung ist (zumindest nicht in einem Kontext, in dem Typensicherheit erwartet wird).
Doc Brown
Das ist richtig. Aus diesem Grund mag ich überladene Methoden oder optionale Parameter.
Knerd
2
Die Verwendung eines Wörterbuchs als Parameter ist eine einfache Methode, um die Anzahl der scheinbaren Parameter auf eine Methode zu reduzieren, verschleiert jedoch die tatsächlichen Abhängigkeiten der Methode. Dies zwingt den Aufrufer, anderswo nach dem zu suchen, was genau im Wörterbuch enthalten sein muss, sei es in Kommentaren, anderen Aufrufen der Methode oder der Methodenimplementierung selbst.
Mike Partridge
Verwenden Sie das Wörterbuch nur für wirklich optionale Parameter, damit grundlegende Anwendungsfälle nicht zu viel Dokumentation lesen müssen.
user949300
0

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:

SetTitle (Document document) { ... }
SetFirstPageBlank (Document document) { ... }
SetMinimumPageCount (Document document) { ... }

Sie können den Builder dann folgendermaßen verwenden:

_builder.SetTitle(document);
_builder.SetFirstPageBlank(document);

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.

CodeART
quelle
0

Ich denke, dass die builderLö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:

  • Organisieren : Es gibt viele Klassen, die ein Dokument auf verschiedene Arten erstellen
  • Beibehalten : Jede Änderung an der Dokumentinstanziierung (wie das Hinzufügen eines neuen Pflichtfelds) führt Sie zu einer Schrotflintenoperation
  • test : Wenn Sie eine Klasse testen, die ein Dokument erstellt, müssen Sie einen Code für das Textfeld hinzufügen, um die Instanziierung des Dokuments zu gewährleisten.

Dies beantwortet jedoch nicht die OP-Frage.

Die Alternative zur Überlastung

Einige Alternativen:

  • Benennen Sie den Namen der Methode : Wenn die gleiche Methode Name wird einige Verwirrung zu schaffen, versuchen , die Methoden benennen Sie einen aussagekräftigen Namen besser zu schaffen , als createDocument, wie: createLicenseDriveDocument, createDocumentWithOptionalFieldsetc. 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.
  • Verwenden Sie statische Methoden . Dieser Ansatz ist im Vergleich zur ersten Alternative ähnlich. Sie können für jeden Fall einen aussagekräftigen Namen verwenden und das Dokument ab einer statischen Methode instanziieren Document, z Document.createLicenseDriveDocument().
  • Gemeinsame Schnittstelle erstellen: Sie können eine einzelne aufgerufene Methode createDocument(InterfaceDocument interfaceDocument)erstellen und verschiedene Implementierungen für erstellen InterfaceDocument. 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.

Dherik
quelle