Besuchermuster mit großer Objekthierarchie verwenden

12

Kontext

Ich habe mit einer Hierarchie von Objekten (einem Ausdrucksbaum) ein "Pseudo" -Besuchermuster verwendet (Pseudo, da darin kein doppelter Versand verwendet wird):

 public interface MyInterface
 {
      void Accept(SomeClass operationClass);
 }

 public class MyImpl : MyInterface 
 {
      public void Accept(SomeClass operationClass)
      {   
           operationClass.DoSomething();
           operationClass.DoSomethingElse();
           // ... and so on ...
      }
 }

Dieses Design war jedoch fragwürdig und recht komfortabel, da die Anzahl der Implementierungen von MyInterface erheblich ist (~ 50 oder mehr) und ich keine zusätzlichen Operationen hinzufügen musste.

Jede Implementierung ist eindeutig (es ist ein anderer Ausdruck oder Operator), und einige sind Verbundwerkstoffe (dh Operatorknoten, die andere Operator- / Blattknoten enthalten).

Das Durchlaufen wird derzeit ausgeführt, indem die Accept-Operation auf dem Stammknoten des Baums aufgerufen wird, der wiederum Accept auf jedem seiner untergeordneten Knoten aufruft, was wiederum ... und so weiter ...

Aber es ist an der Zeit, dass ich einen neuen Vorgang hinzufügen muss , z. B. hübsches Drucken:

 public class MyImpl : MyInterface 
 {
      // Property does not come from MyInterface
      public string SomeProperty { get; set; }

      public void Accept(SomeClass operationClass)
      {   
           operationClass.DoSomething();
           operationClass.DoSomethingElse();
           // ... and so on ...
      }

      public void Accept(SomePrettyPrinter printer)
      {
           printer.PrettyPrint(this.SomeProperty);
      }
 }    

Grundsätzlich sehe ich zwei Möglichkeiten:

  • Behalten Sie das gleiche Design bei und fügen Sie jeder abgeleiteten Klasse auf Kosten der Wartbarkeit eine neue Methode für meine Operation hinzu (keine Option, IMHO).
  • Verwenden Sie das "echte" Besuchermuster auf Kosten der Erweiterbarkeit (keine Option, da ich davon ausgehe, dass weitere Implementierungen auf dem Weg sind ...) mit mehr als 50 Überladungen der Visit-Methode, die jeweils einer bestimmten Implementierung entsprechen ?

Frage

Würden Sie die Verwendung des Besuchermusters empfehlen? Gibt es ein anderes Muster, das zur Lösung dieses Problems beitragen könnte?

T. Fabre
quelle
1
Vielleicht wäre eine Kette von Dekorateuren besser geeignet?
MattDavey
Einige Fragen: Wie unterscheiden sich diese Implementierungen? Wie ist die Hierarchie aufgebaut? und ist es immer die gleiche Struktur? Müssen Sie die Struktur immer in derselben Reihenfolge durchlaufen?
jk.
@MattDavey: Sie würden also empfehlen, einen Dekorateur pro Implementierung und Betrieb zu haben?
T. Fabre
2
@ T.Fabre ist schwer zu sagen. Es gibt mehr als 50 Implementierer von MyInterface.. Haben alle diese Klassen eine eindeutige Implementierung von DoSomethingund DoSomethingElse? Ich sehe nicht, wo Ihre Besucherklasse tatsächlich die Hierarchie durchquert - es sieht facadeim Moment eher wie eine aus .
MattDavey
auch welche Version von C # ist es. Hast du Lambdas? oder linq? zu Ihrer Verfügung
jk.

Antworten:

13

Ich habe das Besuchermuster verwendet, um Ausdrucksbäume über einen Zeitraum von mehr als 10 Jahren in sechs Großprojekten in drei Programmiersprachen darzustellen, und ich bin sehr zufrieden mit dem Ergebnis. Ich habe ein paar Dinge gefunden, die das Anwenden des Musters viel einfacher gemacht haben:

Verwenden Sie keine Überlastungen in der Benutzeroberfläche des Besuchers

Geben Sie den Typ in den Methodennamen ein, dh verwenden Sie

IExpressionVisitor {
    void VisitPrimitive(IPrimitiveExpression expr);
    void VisitComposite(ICompositeExpression expr);
}

eher, als

IExpressionVisitor {
    void Visit(IPrimitiveExpression expr);
    void Visit(ICompositeExpression expr);
}

Fügen Sie Ihrer Besucheroberfläche eine "Fang unbekannt" -Methode hinzu.

Dies würde Benutzern ermöglichen, die Ihren Code nicht ändern können:

IExpressionVisitor {
    void VisitPrimitive(IPrimitiveExpression expr);
    void VisitComposite(ICompositeExpression expr);
    void VisitExpression(IExpression expr);
};

Auf diese Weise könnten sie ihre eigenen Implementierungen erstellen IExpressionund IVisitorihre Ausdrücke "verstehen", indem sie Laufzeittypinformationen bei der Implementierung ihrer Catch-All- VisitExpressionMethode verwenden.

Stellen Sie eine Standard-Do-Nothing-Implementierung der IVisitorSchnittstelle bereit

Auf diese Weise können Benutzer, die sich mit einer Teilmenge von Ausdruckstypen befassen müssen, ihre Besucher schneller erstellen und ihren Code immun gegen das Hinzufügen weiterer Methoden machen IVisitor. Das Schreiben eines Besuchers, der alle Variablennamen aus Ihren Ausdrücken erntet, wird beispielsweise zu einer einfachen Aufgabe, und der Code wird nicht unterbrochen, selbst wenn Sie später eine Reihe neuer Ausdruckstypen hinzufügen IVisitor.

dasblinkenlight
quelle
2
Können Sie erklären, warum Sie sagen Do not use overloads in the interface of the visitor?
Steven Evers
1
Können Sie erklären, warum Sie die Verwendung von Überlastungen nicht empfehlen? Ich habe irgendwo (eigentlich auf oodesign.com) gelesen, dass es nicht wirklich wichtig ist, ob ich Überladungen verwende oder nicht. Gibt es einen bestimmten Grund, warum Sie dieses Design bevorzugen?
T. Fabre
2
@ T.Fabre Es spielt keine Rolle in Bezug auf die Geschwindigkeit, aber es spielt eine Rolle in Bezug auf die Lesbarkeit. Die Methodenauflösung in zwei der drei Sprachen, in denen ich dies implementiert habe ( Java und C #), erfordert einen Laufzeitschritt, um unter den möglichen Überladungen zu wählen, wodurch das Lesen des Codes mit einer großen Anzahl von Überladungen etwas schwieriger wird. Das Refactoring des Codes wird ebenfalls einfacher, da die Auswahl der Methode, die Sie ändern möchten, zu einer trivialen Aufgabe wird.
Dasblinkenlight
@ SnOrfus Bitte siehe meine Antwort auf T. Fabre oben.
Dasblinkenlight
@dasblinkenlight C # bietet jetzt Dynamik, damit die Laufzeit entscheiden kann, welche überladene Methode verwendet werden soll (nicht zur Kompilierungszeit). Gibt es noch einen Grund, warum Sie nicht überladen sollten?
Tintenfiisch