10

ビジターパターンを使用すると、オブジェクトクラスを拡張せずにオブジェクトに対する操作を記述できます。もちろん。しかし、オブジェクトコレクションを外部から操作するグローバル関数または静的クラスを作成しないのはなぜですか?基本的に、Javaのような言語ではaccept()、技術的な理由からメソッドが必要です。しかし、メソッドなしで同じデザインを実装できる言語でaccept()は、Visitorパターンは簡単になりますか?

説明:ビジターパターンでは、ビジタークラス(エンティティ)には.accept()、ビジターの.visit()メソッドを自分で呼び出すことを目的としたメソッドがあります。Javaの例のロジックを見ることができます。訪問者は、サポートする.visit(n)訪問可能なタイプごとに異なるメソッドを定義し、実行時にそれらの中から選択するためにトリックを使用する必要があります。しかし、pythonやphpのような言語には動的型付けがあり、メソッドのオーバーロードはありません。私が訪問者である場合、エンティティのタイプやメソッドの完全な署名さえ知らなくても、エンティティメソッド(たとえば)を呼び出すことができます。(それは「ダブルディスパッチ」の問題ですよね?)n.accept().serialize()

acceptメソッドが保護されたデータを訪問者に渡すことができることは知っていますが、ポイントは何ですか?データがビジタークラスに公開されている場合、その詳細はクラスの外部で重要になるため、データは事実上クラスインターフェイスの一部になります。とにかく、個人データを公開することは、訪問者のパターンのポイントとして私を襲うことはありませんでした。

つまり、python、ruby、またはphpでは、visitedオブジェクトにacceptメソッドを使用せずに(そしてリフレクションなしで)、visitorのようなクラスを実装できるようです。「visited」クラスの協力なしに、異種オブジェクトのファミリーを操作してそれらのパブリックメソッドを呼び出すことができる場合でも、これは「Visitorパターン」と呼ばれるに値しますか?私が見逃しているパターンの本質に何かありますか、それとも単に「オブジェクトを外部から操作して操作を実行する新しいクラスを作成する」ということですか?

PS。私はSOや他の場所でたくさんの議論を見てきましたが、この質問に対処するものは何も見つかりませんでした。ポインタは大歓迎です。

4

6 に答える 6

2

Visitor が特に役立つ場所は、Visitor が Visitee のタイプをオンにする必要がある場合であり、何らかの理由で、その知識を Visitee にエンコードしたくない場合です (プラグイン アーキテクチャを考えてください)。次の Python コードを検討してください。

ビジタースタイル

class Banana(object):
      def visit(self, visitor):
          visitor.process_banana(self) 

class Apple(object):
      def visit(self, visitor):
          visitor.process_apple(self) 

class VisitorExample(object):
      def process_banana(self, banana):
          print "Mashing banana: ", banana

      def process_banana(self, apple):
          print "Crunching apple: ", apple

(基本クラス/ミックスインで訪問者ロジックを圧縮できることに注意してください)。

と比べて:

来客なしスタイル

class NonVisitorVisitor(object):
      def process(self, fruit):
          verb = {Banana: "Mashing banana: ", 
                  Apple: "Crunching apple: "}[type(fruit)]
          print verb, fruit

2 番目の例では、果物は「訪問者」の特別なサポートを必要とせず、「訪問者」は特定のタイプのロジックがないことを処理します。

対照的に、Java または C++ では 2 番目の例は実際には不可能であり、visit メソッド (visitees 内) は 1 つの名前を使用して process メソッドのすべてのバージョンを参照できます。コンパイラは、渡される型に適用されるバージョンを選択します。また、訪問者は、訪問者のタイプのルート クラスにデフォルトの実装を簡単に提供できます。また、訪問先のメソッド用に生成されたコードでコンパイル時にメソッド バリアント ( process(Banana b)vsなど) が選択されるため、訪問先に Visitメソッドが必要です。process(Apple a)visit

したがって、Python や Ruby など、パラメーターの型にディスパッチがない (つまり、プログラマーが自分で実装する必要がある) 言語では、ビジター パターンは必要ありません。あるいは、訪問者メソッドを介したディスパッチなしで訪問者パターンをより適切に実装すると言う人もいるかもしれません。

一般に、Python、Ruby、または Smalltalk などの動的言語では、「visitee」クラスに必要なすべての情報 (ここでは動詞が適用されます) を持たせ、必要に応じて「visitor」をサポートするフックを提供することをお勧めします。コマンドまたは戦略パターンとして使用するか、ここに示す非訪問者パターンを使用します。

結論

non-Visitor は型切り替えロジックを実装するクリーンな方法ですが、明示的な型切り替えは通常コードの匂いがします。これを行う Java および C++ の方法も、ビジターでの明示的な切り替えであることを思い出してください。これらの言語のパターンの優雅さは、訪問者に明示的な切り替えロジックを持たないようにすることです。これは、型指定されていない変数を持つ動的言語では不可能です。したがって、静的言語の Visitor パターンが回避しようとする罪を再現するため、上部の Visitor パターンは動的言語には適していません。

パターンを使用する場合は、UML ダイアグラムを無作法に再現するのではなく、パターンが達成しようとしていることと、具体的に検討中の言語機構を使用してそれらの目標をどのように達成するかを理解する必要があります。この場合、同じメリットを達成するためのパターンが異なって見え、呼び出しのパターンが異なります。そうすることで、それらをさまざまな言語に適応させることができますが、同じ言語内のさまざまな具体的な状況にも適応させることができます.

更新: このパターンの実装に関する Ruby の記事は次のとおりです: http://blog.rubybestpractices.com/posts/aaronp/001_double_dispatch_dance.html

二重ディスパッチは、私にはかなり強制されているようです。私が知る限り、あなたはそれをやめることができます。

于 2015-04-17T10:48:54.237 に答える
1

この回答はPHPなどを知らずに作成されていますが、訪問者は通常、エンティティで複数のメソッド(「シリアル化」に言及しました)を呼び出す必要があります。具体的な Visitor で Visit() メソッドが呼び出されると、Visitor はエンティティのサブタイプごとに明確に異なるコードを実行できます。それが動的型付け言語とどのように違うのかわかりません (ただし、フィードバックがあれば幸いです)。

Visitor のもう 1 つの優れた利点は、エンティティを列挙するコードから、各エンティティで実行されるコードを明確に分離できることです。これにより、少なくとも 1 つの大きなプロジェクトで深刻なコードの重複を回避できました。

余談ですが、私はメソッドのオーバーロードを持たない言語で Visitor を使用しました。Visit(TypeN n) を VisitN(TypeN n) に置き換えるだけです。


コメントからのフォローアップ。

これはビジター疑似コードであり、ビジター オブジェクトの協力なしで (少なくともスイッチ ブロックなしで) どうすればよいかわかりません。

abstract class ScriptCommand
{
   void Accept(Visitor v);
}

abstract class MoveFileCommand
{
   string TargetFile;
   string DestinationLocation;

   void Accept(Visitor v)
   {
      v.VisitMoveFileCmd(this);  // this line is important because it eliminates the switch on object type
   }
}

abstract class DeleteFileCommand
{
   string TargetFile;

   void Accept(Visitor v)
   {
      v.VisitDeleteFileCmd(this); // this line is important because it eliminates the switch on object type

   }
}

// etc, many more commands

abstract class CommandVisitor
{
   void VisitMoveFileCmd(MoveFileCommand cmd);
   void VisitDeleteFileCmd(DeleteFileCommand cmd);
   // etc
}

// concrete implementation

class PersistCommandVisitor() inherits CommandVisitor
{
   void VisitMoveFileCmd(MoveFileCommand cmd)
   {
      // save the MoveFileCommand instance to a file stream or xml doc
      // this code is type-specific because each cmd subtype has vastly
      // different properties
   }

   void VisitDeleteFileCmd(DeleteFileCommand cmd)
   { 
      // save the DeleteFileCommand instance to a file stream or xml doc
      // this code is type-specific because each cmd subtype has vastly
      // different properties
   }

}

ビジター インフラストラクチャを使用すると、ケースを選択せず​​に、swithc などのさまざまなコマンド サブタイプを処理できます。

列挙を処理する訪問者に関しては、そのように自分自身を制限していると思います。協力するクラス (抽象 VisitorEnumerator) が関与できないと言っているわけではありません。

たとえば、次の訪問者は列挙の順序を認識していないことに注意してください。

class FindTextCommandVisitor() inherits CommandVisitor
{
   string TextToFind;
   boolean TextFound = false;

   void VisitMoveFileCmd(MoveFileCommand cmd)
   {
      if (cmd.TargetFile.Contains(TextToFind) Or cmd.DestinationLocation.Contains(TextToFind))
         TextFound = true;
   }


   void VisitDeleteFileCmd(DeleteFileCommand cmd)
   { 
      // search DeleteFileCommand's properties
   }

}

そして、これにより、次のように再利用できます。

ScriptCommand FindTextFromTop(string txt)
{
   FindTextCommandVisitor v = new FindTextCommandVisitor();
   v.TextToFind = txt;
   for (int cmdNdx = 0; cmdNdx < CommandList.Length; cmdNdx++)
   {
      CommandList[cmdNdx].Accept(v);
      if (v.TextFound)
         return CommandList[cmdNdx];  // return the first item matching
   }
}

同じ訪問者を逆に列挙します。

ScriptCommand FindTextFromBottom(string txt)
{
   FindTextCommandVisitor v = new FindTextCommandVisitor();
   v.TextToFind = txt;
   for (int cmdNdx = CommandList.Length-1; cmdNdx >= 0; cmdNdx--)
   {
      CommandList[cmdNdx].Accept(v);
      if (v.TextFound)
         return CommandList[cmdNdx];  // return the first item matching
   }
}

実際のコードでは、列挙子の基本クラスを作成し、それをサブクラス化してさまざまな列挙シナリオを処理し、具体的な Visitor サブクラスを渡してそれらを完全に分離します。うまくいけば、列挙を分離しておくことの威力を理解していただけると思います。

于 2012-06-22T13:24:41.880 に答える
0

たぶん、それは言語に依存します。

ビジター パターンは、 multiple-dispatchを備えていない言語での二重および複数階層の問題を解決します。Ruby、Lisp、Python を例にとってみましょう。それらはすべて動的型付け言語ですが、CLOS-Lisp だけが標準で複数ディスパッチを実装しています。これはマルチメソッドとも呼ばれ、Python や Ruby では拡張機能を使用して実装できるようです。

ウィキペディアで次のように述べている興味深いコメントが気に入っています。

複数のディスパッチを備えた Lisp のオブジェクト システム [CLOS] は、Visitor パターンに取って代わるものではなく、パターンがほとんどなくなる、より簡潔な実装を提供するだけです。

他の言語では、静的に型付けされた言語であっても、マルチメソッドが存在しないことを回避する必要があります。Visitor パターンはそのような方法の 1 つです。

于 2012-06-22T11:19:12.313 に答える
0

Visitor Pattern と Double Dispatch を同じ意味で使用していると思います。あなたが言う時、

異種オブジェクトのファミリを操作し、「訪問済み」クラスからの協力なしにそれらのパブリック メソッドを呼び出すことができる場合、これは「ビジター パターン」と呼ばれるに値しますか?

外部からオブジェクトを操作して操作を実行する新しいクラスを作成します。」

あなたはダブルディスパッチとは何かを定義しています。確かに、ビジターパターンはダブルディスパッチで実装されています。しかし、パターン自体にはそれ以上のものがあります。

  • 各訪問者は要素 (エンティティ) のグループに対するアルゴリズムであり、既存のコードを変更せずに新しい訪問者をプラグインできます。オープン/クローズの原則。
  • 新しい要素が頻繁に追加される場合、訪問者パターンは避けるのが最善です
于 2012-06-22T12:06:40.717 に答える