Besucherstabilität vs. Flexibilität

8

Ich arbeite an einer GUI-Anwendung, die eine Konfigurationsdatei generiert. Ich habe eine Klassenhierarchie für das Konfigurationsmodell und verwende einen Objektbaum dieser Hierarchie in verschiedenen Kontexten. Derzeit verwende ich das Besuchermuster, um zu vermeiden, dass meine Modellklassen mit kontextspezifischem Code verschmutzt werden.

interface IConfigurationElement {
    void acceptVisitor(IConfigurationElementVisitor visitor);
}

In einer früheren Version habe ich instanceofanstelle des Besuchers Bedingungsketten verwendet. Beim Vergleich der beiden Ansätze sehe ich die folgenden Nachteile.

Besucher

  • Es ist einfacher und sicherer, neue hinzuzufügen IConfigurationElement. Fügen Sie einfach eine neue Deklaration hinzu IConfigurationElementVisitorund der Compiler generiert Fehler für alle Besucherimplementierungen. Bei instanceofKetten müssen Sie sich alle Stellen merken, die Sie mit dem neuen Konfigurationselement erweitern müssen. instanceofVerstößt grundsätzlich gegen das DRY-Prinzip, da es die Logik an mehreren Stellen dupliziert.
  • Das Besuchermuster ist effizienter als eine Kette von instanceofBedingungen

Instanz von

  • Der große Vorteil von instanceofist seine Flexibilität. So instanceof kann ich beispielsweise spezielle Lösungen für verschiedene Teilmengen von IConfigurationElementImplementierungen definieren, die in einigen Fällen ähnlich behandelt werden müssen. Im Gegensatz dazu zwingt mich Visitor, jedes Mal eine Methode für jede Implementierungsklasse zu implementieren.

Gibt es eine gemeinsame Lösung für diese Art von Problem? Kann ich den Besucher irgendwie anpassen, damit ich in einigen Fällen eine gemeinsame Lösung anbieten kann?

Johannes Luong
quelle
Ich kann Ihnen dieses Blog-Tutorial nur empfehlen, das die verschiedenen Besucherstile (instanceOf usw.) vergleicht und sehr interessante Antworten liefert. Es hat mir geholfen. Ich hoffe, es funktioniert auch für Sie: apocalisp.wordpress.com/2009/08/21/…
AndreasScheinert

Antworten:

1

Sie können Besucher mit instanceOf verwenden

Schnittstellen:

interface Visitable {
  void accept(Object visitor);
}
interface AVisitor {
  void visitA(A a);
}
interface BVisitor {
  void visitB(B b);
}
interface CVisitor {
  void visitB(C c);
}

Visitables:

class C implements Visitable {
  public void accept(Object visitor) {
    if (visitor instanceof CVisitor) {
      ((BVisitor)vistor).visitC(this);
    }
  }
}

class B implements Visitable {
  public void accept(Object visitor) {
    if (visitor instanceof BVisitor) {
      ((BVisitor)vistor).visitB(this);
    }
  }
}

class A extends B implements Visitable {
  public void accept(Object visitor) {
    super.accept(visitor);
    if (visitor instanceof AVisitor) {
      ((AVisitor)vistor).visitA(this);
    }
  }
}

Besucher:

class PrintBs implements BVisitor {
  public void visitB(B b) {
    system.out.println(b);
  }
}

class PrintAs implements AVisitor {
  public void visitA(A a) {
    system.out.println(a);
  }
}

class PrintCs implements CVisitor {
  public void visitC(C c) {
    system.out.println(c);
  }
}
class PrintAsAndCs implements CVisitor, AVisitor{
  public void visitA(A a) {
    system.out.println(a);
  }
  public void visitC(C c) {
    system.out.println(c);
  }
}

Jede Klasse kennt nur die zugehörigen Schnittstellen. Wenn Sie also neue Besucher oder Besucher hinzufügen möchten, müssen Sie alles in dieser Kategorie (Besucher / Besucher) ändern (für Besucher muss nichts geändert werden, für Besucher muss eine neue Besucheroberfläche erstellt werden, aber nein Änderung bestehender Objekte).

Auf diese Weise gibt es keine Testinstanzkette, und Besucher für Teilmengen müssen nicht einmal über Typen außerhalb dieser Teilmenge Bescheid wissen.

Die Frage ist, was mit der Situation zu tun ist, in der A B erweitert (und B auch sichtbar ist). In diesem Fall könnten Sie einfach super.accept (Besucher) zu accept hinzufügen (es wäre also eine kurze Kette von Instanzen von s, aber nur als Solange die Hierarchie tief ist und nicht zu tief sein sollte, um eine Rolle zu spielen, müssen Sie sie nicht vollständig manuell schreiben.

user470365
quelle
Wenn ich Sie richtig verstehe, schlagen Sie vor, zu entfernen IConfigurationElementVisitorund nur nach bestimmten Besuchertypen zu suchen Visitable. Obwohl dies eine mögliche Lösung ist, sehe ich einige Nachteile. Erstens würde dies die Stabilität des Besuchers beseitigen, über den ich gesprochen habe, und zweitens würde ich das Wissen über Besucher in meine VisitableImplementierungen einbeziehen, was zu vermeiden ein Teil des Grundes war, Besucher überhaupt zu verwenden.
Johannes Luong
Nicht in Visitable (das sollte nur eine einfache Schnittstelle mit accept (Objektbesucher) sein), sondern in Implementierungen von Visitable. Daher prüft jede Klasse nur ihren Besucher, sodass sie nur weiß, dass eine Schnittstelle für den Besuch vorhanden ist. Ich werde meine Antwort ändern, um dies klar zu machen.
user470365
Ok, Ihr Ansatz ermöglicht es, Besucher zu haben, die nur bestimmte Untertypen meiner Struktur besuchen. Das ist eine schöne Idee. Was ich will, ist etwas anders (ich sollte wahrscheinlich meine Beschreibung aktualisieren). Ich möchte eine Garantie für die Kompilierungszeit, dass alle Elemente meiner Struktur besucht wurden, aber gleichzeitig möchte ich einige Elemente gleich und andere unterschiedlich behandeln. Es ist wie @AndreasScheinert es ausdrückte. Grundsätzlich möchte ich einen Mustervergleich mit einer Compiler-Warnung, wenn der Abgleich nicht vollständig ist (wie Scalas Abgleich mit Fallklassen).
Johannes Luong
0

Nun ja, du könntest. Erfassen Sie die Gemeinsamkeiten der vielen Konfigurationselemente, indem Sie ihnen Rollen zuweisen. Möglicherweise erhalten Sie eine Instanz von, jedoch nicht in einer Weise, die gegen das DRY-Prinzip verstößt, sondern um die statische Typisierung von Java zu umgehen.

class ConfigurationElementA implements IConfigurationElement, ICommittable {
}

class ConfigurationElementB implements IConfigurationElement, IVerifiable {
}

class Visitor {
    void accept(IConfigurationElement element) {
        if (element instanceof ICommittable) {
            // ...
        }

        // Note: not a chain of instanceofs.

        if (element instanceof IVerifiable) {
            // ...
        }
    }
}

Mit anderen Worten, Sie lassen den Besucher Konfigurationselemente generisch akzeptieren und über Rollen auf Gruppen von Implementierungen reagieren. Beachten Sie, dass Sie so spezifisch vorgehen können, wie Sie möchten, indem Sie Ihre Konfigurationselemente entsprechend modellieren.

Sie können hier das RoleInterface- Muster von Martin Fowler erkennen .

Mihai Danila
quelle
1
Grundsätzlich verwenden Sie Schnittstellen, um Teilmengen zu definieren IConfigurationElement, die speziell basierend auf der festgelegten Mitgliedschaft verarbeitet werden können. Sobald Sie Objekte mit instanceofIhrem Compiler unterscheiden, kann Ihnen dies leider nicht weiterhelfen, wenn Sie vergessen, einen Ihrer Besucher zu aktualisieren. Ihre Lösung hat den Vorteil, dass alle instanceofOperatoren in einen gemeinsamen Typ eingebettet sind. Dies hilft, sie zu finden, indem Sie nach dem Typ suchen.
Johannes Luong
Mein Vorschlag basiert auf zwei Beobachtungen zu Ihrem Anwendungsfall: Erstens möchten Sie eine Verbreitung von acceptMethodensignaturen vermeiden . Zu diesem Zweck habe ich einen Catch-All-Ansatz vorgeschlagen, den Sie an Ihre Bedürfnisse anpassen können und der mehr oder weniger spezifisch für Ihre IConfigurationElementImplementierung ist. Zweitens wollten Sie die Flexibilität der Instanz von, vermutlich (im ersten Fall), weil Sie gemeinsame Merkmale in Ihren Implementierungsklassen haben - ansonsten kein Grund, die acceptProliferation zunächst zu vermeiden . Dort habe ich vorgeschlagen, RoleInterfacewas instanceofanders verwendet wird.
Mihai Danila
(Sie können immer noch Folgendes vermeiden instanceof: IConfigurationElementImplementieren Sie IRoleEnabledund lassen Sie den Besucher jedes Konfigurationselement als rollenaktiviertes Element aufrufen visitRoleEnabledund visitRoleEnabledin jeder Implementierungsklasse den Besucher für jede der implementierten Rollen zurückrufen. Aber damit beginnen wir wild auf, instanceofwenn es wirklich gut läuft.)
Mihai Danila
0

Ich kann mir einige mögliche Lösungen vorstellen:

  1. Erstellen Sie eine private Methode in der VisitorImplementierung und lassen Sie mehrere visitMethoden in der VisitorImplementierung diese private Methode aufrufen.

  2. Wenn das oben Gesagte an vielen Stellen wiederholt wird, können Sie eine abstrakte Klasse erstellen Visitor, die eine Teilmenge von visitImplementierungen implementiert und auf eine gemeinsame protected abstractMethode umleitet .

  3. Erstellen Sie mehrere VisitorSchnittstellen:

    interface AOrB extends IConfigurationElementVisitor {
        <Result> Result accept(AOrBVisitor<Result> visitor);
    
        interface AOrBVisitor<Result> {
            Result visit(A a);
            Result visit(B b);
        }
    }
    
    interface ThisCouldBeOfTypeB extends IConfigurationElementVisitor {
        <Result> Result accept(ThisCouldBeOfTypeBVisitor<Result> visitor);
    
        interface ThisCouldBeOfTypeBVisitor<Result> {
            Result visit(B b);
            Result visit(ThisCouldBeOfTypeB visitable);
        }
    }
    
    class A implements AOrB, ThisCouldBeOfTypeB {...}
    
    class B implements AOrB, ThisCouldBeOfTypeB {...}
    
    class C implements ThisCouldBeOfTypeB {...}

Ich denke, 3 ist das, wonach Sie suchen. Es ist 100% statischer Polymorphismus und generiert Compiler-Warnungen, wenn ein Typ nicht behandelt wird. Der einzige Nachteil, den ich mir vorstellen kann, ist, dass die Wartung der Visitable*Implementierungen komplex werden kann, wenn viele verschiedene Visitable*Schnittstellen vorhanden sind.

Eric
quelle