訪問者の安定性とインスタンスの柔軟性


8

設定ファイルを生成するGUIアプリケーションに取り組んでいます。構成モデルのクラス階層があり、その階層のオブジェクトツリーをいくつかの異なるコンテキストで使用しています。現在、ビジターパターンを使用して、コンテキスト固有のコードでモデルクラスを汚染しないようにしています。

interface IConfigurationElement {
    void acceptVisitor(IConfigurationElementVisitor visitor);
}

以前のバージョンinstanceofでは、ビジターの代わりに一連の条件を使用していました。2つのアプローチを比較すると、次のトレードオフが見られます。

ビジター

  • 新しいを追加する方が簡単で安全IConfigurationElementです。新しい宣言をに追加するだけIConfigurationElementVisitorで、コンパイラはすべてのビジター実装に対してエラーを生成します。ではinstanceofチェーンあなたは、新しい構成要素を拡張する必要があるすべての場所を覚えておく必要があります。instanceof複数の場所でロジックを複製するため、基本的にDRY原則に違反します。
  • 訪問者パターンは一連の条件よりも効率的ですinstanceof

instanceof

  • の大きな利点instanceofは、その柔軟性です。たとえば、場合instanceof によっては同様にIConfigurationElement処理する必要がある実装のさまざまなサブセットに対して特別なソリューションを定義できます。対照的に、Visitorでは毎回、各実装クラスのメソッドを実装する必要があります。

この種の問題に対する一般的な解決策はありますか?どういうわけかビジターを適応させることができるので、いくつかのケースに共通のソリューションを提供できますか?


私は非常にそれが私はそれがあまりにもあなたのための中華鍋を願って私を助けてくれ、あなたに別の訪問者のスタイル(INSTANCEOFなど)を比較して非常に興味深い回答を提供し、このブログのチュートリアルをお勧めします:apocalisp.wordpress.com/2009/08/21/...
AndreasScheinertを

回答:


1

instanceOfでビジターを使用できます

インターフェース:

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);
    }
  }
}

訪問者:

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);
  }
}

各クラスは関連するインターフェースのみを認識しているため、新しいビジターまたはビジタブルを追加するには、そのカテゴリ(ビジター/ビジター)のすべてを変更する必要があります(ビジターの場合は何も変更する必要はありません。ビジブルの場合は新しいビジターインターフェースを作成する必要がありますが、ここでも既存のオブジェクトの変更)。

このように、instanceofテストのチェーンはなく、サブセットのビジターはこのサブセット外の型について知る必要さえありません。

質問は、AがBを拡張する(そしてBもVisitableである)状況で何をするかです。その場合、super.accept(visitor)をacceptに追加するだけです(したがって、instanceof-sの短いチェーンになりますが、onlu as階層が深い限り、そしてそれが問題になるほど深くないはずですが、手動で全体を記述する必要はありません)。


私があなたを正しく理解している場合は、IConfigurationElementVisitorで特定の訪問者タイプを削除して確認することを提案しますVisitable。これは可能な解決策ですが、いくつかの欠点があります。最初にこれは私が話した訪問者の安定性を削除し、次に私は訪問者についての知識を私のVisitable実装に引き込みます。これを避けることは最初に訪問者を使用する理由の一部でした。
Johannes Luong 2013年

Visitableにはありません(これは、accept(Object visitor)の単純なインターフェースでなければなりません)が、Vistableの実装にあります。そのため、各クラスはそのビジターのみをチェックするため、クラスを訪問するためのインターフェースが存在するという事実についてのみ認識します。これを明確にするために、回答を変更します。
user470365 2013年

わかりました、あなたのアプローチは私の構造の特定のサブタイプのみを訪問する訪問者を許可します。これはいい考えです。私が欲しいのは少し異なります(おそらく説明を更新する必要があります)。私が訪れた構造のすべての要素がコンパイル時に保証されることを望みますが、同時に、いくつかの要素を同等に扱い、異なるものを扱いたいと思っています。@AndreasScheinertが言ったようなものです。基本的に、マッチが完全ではない場合(Scalaのケースクラスとのマッチなど)は、コンパイラーの警告を使用したパターンマッチングが必要です。
Johannes Luong 2013年

0

ええ、そうです。多くの構成要素に役割を割り当てることにより、それらに共通性を持たせます。あなたはinstanceofに終わるかもしれませんが、DRYの原則に違反する方法ではなく、Javaの静的型付けを回避する方法としてです。

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) {
            // ...
        }
    }
}

言い換えると、訪問者に構成要素を総称的に受け入れさせ、ロールを介して実装のグループに作用させることができます。それに応じて構成要素をモデル化することで、必要に応じて具体的にすることができます。

ここで、Martin FowlerのRoleInterfaceパターンを認識できます。


1
基本的にインターフェースを使用してIConfigurationElement、セットのメンバーシップに基づいて具体的に処理できるサブセットを定義します。残念ながら、instanceofコンパイラでオブジェクトを区別するとすぐに、ビジターの1人を更新し忘れた場合は役に立ちません。このソリューションには、すべてのinstanceof演算子が共通の型に埋め込まれているという利点があります。これにより、型を検索して演算子を見つけることができます。
Johannes Luong 2013

私の提案は、ユースケースに関する2つの観察結果に基づいています。最初に、acceptメソッドシグネチャの急増を避けたいと考えています。このため、私はキャッチオールアプローチを提案しました。これは、ニーズに合わせて調整でき、IConfigurationElement実装の前後で多少具体的になります。2番目に、おそらく(最初のケースでは)実装クラスに共通の特性があるため、instanceofの柔軟性が必要acceptでした。それは私がRoleInterfaceどのようにinstanceof異なる使い方をするかを提案した場所です。
Mihai Danila、2013

(それでも回避できますinstanceofIConfigurationElement実装IRoleEnabledを作成し、訪問者に各構成要素を役割対応アイテム呼び出しとして呼び出しvisitRoleEnabledvisitRoleEnabled各実装クラスで、実装された役割ごとに訪問者にコールバックさせます。しかしこれで、instanceofそれが本当にうまくいっているときにワイルドになります。)
ミハイダニーラ

0

私はいくつかの潜在的な解決策を考えることができます:

  1. Visitor実装でプライベートメソッドを作成し、実装で複数のvisitメソッドを使用してVisitorそのプライベートメソッドを呼び出します。

  2. 上記が多くの場所で繰り返される場合、Visitorvisit実装し、実装のサブセットを共通のprotected abstractメソッドにリダイレクトする抽象クラスの作成を検討できます。

  3. 複数のVisitorインターフェースを作成します。

    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 {...}

3はあなたが探しているものだと思います。100%静的なポリモーフィズムであり、型が処理されない場合はコンパイラ警告が生成されます。しかし、私が考えることができる唯一の欠点Visitable*は、さまざまなVisitable*インターフェースが多数ある場合、実装の維持が複雑になる可能性があることです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.