大きなオブジェクト階層での訪問者パターンの使用


12

環境

オブジェクトの階層(式ツリー)で「疑似」ビジターパターン(二重ディスパッチを使用しないように疑似)を使用しています。

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

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

ただし、MyInterfaceの実装の数は非常に多く(〜50以上)、余分な操作を追加する必要がないため、この設計は疑わしく、かなり快適でした。

各実装は一意であり(異なる式または演算子です)、一部は複合(つまり、他の演算子/リーフノードを含む演算子ノード)です。

トラバーサルは現在、ツリーのルートノードでAccept操作を呼び出すことによって実行され、その操作は、その子ノードのそれぞれでAcceptを呼び出します。

しかし、きれいな印刷などの新しい操作追加する必要があるときが来ました。

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

基本的に2つのオプションがあります:

  • 保守性を犠牲にして、同じ設計を維持し、操作の新しいメソッドを各派生クラスに追加します(オプションではなく、IMHO)
  • 「真の」訪問者パターンを使用して、拡張性を犠牲にします(オプションではなく、今後実装が増える予定です...)、Visitメソッドの約50以上のオーバーロードで、それぞれが特定の実装に一致します?

質問

Visitorパターンを使用することをお勧めしますか?この問題を解決するのに役立つ他のパターンはありますか?


1
おそらく、デコレータのチェーンがより適切でしょうか?
MattDavey

いくつかの質問:これらの実装はどのように異なりますか?階層の構造は何ですか?そして、それは常に同じ構造ですか?常に同じ順序で構造を横断する必要がありますか?
jk。

@MattDavey:実装と操作ごとに1つのデコレータを使用することをお勧めしますか?
T.ファーブル

2
@ T.Fabreそれを伝えるのは難しいです。MyInterface..の実装者は50以上あります。これらのクラスはすべて、DoSomethingおよびの一意の実装を持っていDoSomethingElseますか?あなたの訪問者のクラスは、実際に階層を横断ところ、私は表示されません-それはより多くのように見えるfacade瞬間に...
MattDavey

また、C#のバージョンは何ですか。ラムダはありますか?またはlinq?あなたの処分で
jk。

回答:


13

私は、訪問者パターンを使用して、3つのプログラミング言語での6つの大規模プロジェクトで10年以上にわたって式ツリーを表現してきましたが、その結果に非常に満足しています。パターンを簡単に適用できるようにするものがいくつか見つかりました。

訪問者のインターフェースでオーバーロードを使用しないでください

型をメソッド名に入れます。つまり、使用します

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

のではなく

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

訪問者インターフェイスに「キャッチ不明」メソッドを追加します。

それはあなたのコードを変更できないユーザーにとって可能になるでしょう:

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

これは、彼らが独自の実装を構築できますIExpressionし、IVisitor彼らのキャッチオールの実装では実行時の型情報を使って、自分の表現を「理解」というVisitExpression方法。

IVisitorインターフェースのデフォルトの無処理実装を提供する

これにより、式タイプのサブセットを処理する必要があるユーザーは、訪問者をより迅速に構築でき、コードをに追加できますIVisitor。たとえば、式からすべての変数名を収集するビジターを作成するのは簡単な作業になり、新しい式の種類をIVisitor後で追加してもコードは壊れません。


2
あなたが言う理由を明確にできますDo not use overloads in the interface of the visitorか?
スティーブンエバーズ

1
オーバーロードを使用しないほうがよい理由を説明できますか?私はどこか(実際にはoodesign.comで)を読みましたが、オーバーロードを使用するかどうかは本当に重要ではありません。そのデザインを好む特定の理由はありますか?
T.ファーブル

2
@ T.Fabre速度の点では重要ではありませんが、読みやすさの点では重要です。これを実装した3つの言語のうち2つ(JavaとC#)でのメソッド解決には、潜在的なオーバーロードを選択するためのランタイムステップが必要です。コードのリファクタリングも簡単になります。変更するメソッドを選択するのは簡単な作業になるためです。
dasblinkenlight

@SnOrfus上記のT.Fabreに対する私の答えをご覧ください。
dasblinkenlight

@dasblinkenlight C#は、(コンパイル時ではなく)使用するオーバーロードされたメソッドをランタイムが決定できるように、ダイナミックを提供します。オーバーロードを使用しない理由はまだありますか?
Tintenfiisch
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.