いつビジターデザインパターンを使用すればよいですか?[閉まっている]


315

私はブログでビジターパターンへの言及を見続けていますが、認めざるを得ません。私はパターンについてウィキペディアの記事を読み、そのメカニズムを理解しましたが、それをいつ使用するかについてはまだ混乱しています。

最近本当にデコレータパターンを取得し、今やあらゆる場所で使用されている人として、この一見便利なパターンも直感的に理解できるようになりたいと思っています。


7
ロビーで2時間待たされたまま、私のブラックベリーについてのJermey Millerによるこの記事を読んだ後、最後にそれを手に入れました。長いですが、二重ディスパッチ、ビジター、コンポジット、およびこれらを使用して何ができるかについて素晴らしい説明があります。
ジョージマウアー


3
訪問者パターン?どれ?重要なのは、このデザインパターンの周りに多くの誤解と純粋な混乱があることです。:私は書かれており、うまくいけば、この混乱にいくつかの順序を置く記事ましrgomes-info.blogspot.co.uk/2013/01/...
リチャード・ゴメス

unionデータ型の関数オブジェクトが必要な場合は、ビジターパターンが必要です。関数オブジェクトと共用体データ型が何であるか不思議に思うかもしれませんが、それはccs.neu.edu/home/matthias/htdc.htmlを
Wei Qiu

回答:


315

訪問者のパターンにはあまり詳しくありません。私がそれが正しいかどうか見てみましょう。動物の階層があるとします

class Animal {  };
class Dog: public Animal {  };
class Cat: public Animal {  };

(これは、確立されたインターフェースを持つ複雑な階層であると想定します。)

次に、階層に新しい操作を追加します。つまり、各動物に音を出させます。階層がこのように単純である限り、単純なポリモーフィズムでそれを行うことができます。

class Animal
{ public: virtual void makeSound() = 0; };

class Dog : public Animal
{ public: void makeSound(); };

void Dog::makeSound()
{ std::cout << "woof!\n"; }

class Cat : public Animal
{ public: void makeSound(); };

void Cat::makeSound()
{ std::cout << "meow!\n"; }

しかし、このようにして、操作を追加するたびに、階層のすべての単一クラスのインターフェースを変更する必要があります。ここで、代わりに元のインターフェースに満足しており、変更を最小限にしたいとします。

Visitorパターンを使用すると、新しい各操作を適切なクラスに移動でき、階層のインターフェースを1回だけ拡張する必要があります。やってみましょう。最初に、階層内のすべてのクラスのメソッドを持つ抽象操作(GoFの「Visitor」クラス)を定義します。

class Operation
{
public:
    virtual void hereIsADog(Dog *d) = 0;
    virtual void hereIsACat(Cat *c) = 0;
};

次に、新しい操作を受け入れるために階層を変更します。

class Animal
{ public: virtual void letsDo(Operation *v) = 0; };

class Dog : public Animal
{ public: void letsDo(Operation *v); };

void Dog::letsDo(Operation *v)
{ v->hereIsADog(this); }

class Cat : public Animal
{ public: void letsDo(Operation *v); };

void Cat::letsDo(Operation *v)
{ v->hereIsACat(this); }

最後に、CatもDogも変更せずに、実際の操作を実装します。

class Sound : public Operation
{
public:
    void hereIsADog(Dog *d);
    void hereIsACat(Cat *c);
};

void Sound::hereIsADog(Dog *d)
{ std::cout << "woof!\n"; }

void Sound::hereIsACat(Cat *c)
{ std::cout << "meow!\n"; }

これで、階層を変更せずに操作を追加する方法ができました。これがどのように機能するかです:

int main()
{
    Cat c;
    Sound theSound;
    c.letsDo(&theSound);
}

19
S.Lott、木を歩くことは実際にはビジターパターンではありません。(これは「階層的なビジターパターン」であり、紛らわしいほど完全に異なります。)継承またはインターフェース実装を使用せずにGoFビジターパターンを表示する方法はありません。
10

14
@Knownasilya-それは真実ではありません。&-Operatorは、インターフェースが必要とするSound-Objectのアドレスを提供します。letsDo(Operation *v) ポインタが必要です。
AquilaRapax 2013

3
わかりやすくするために、このビジターデザインパターンの例は正しいですか?
ゴジラ2013

4
多くのことを考えた後、すでにDogとCatをメソッドに渡しているにもかかわらず、なぜhereIsADogとhereIsACatの2つのメソッドを呼び出したのかと思います。私は単純なperformTask(Object * obj)を好み、このオブジェクトをOperationクラスにキャストします。(およびオーバーライドをサポートする言語で、キャストする必要はありません)
Abdalrahman Shatou 2013

6
最後の「メイン」の例ではtheSound.hereIsACat(c)、仕事を終えたでしょうが、パターンによって生じるすべてのオーバーヘッドをどのように正当化しますか?二重発送は正当化です。
franssu 2014

131

あなたの混乱の理由は、おそらくビジターが致命的な誤称であることです。多くの(著名な1!)プログラマーがこの問題に遭遇しました。それが実際に行うことは、それをネイティブにサポートしない言語でダブルディスパッチを実装することです(それらのほとんどはサポートしていません)。


1)私のお気に入りの例は、「Effective C ++」の著名な作家であるScott Meyersで、これを彼の最も重要なC ++の 1つと呼びましたこれまでの瞬間


3
+1「パターンはありません」-完璧な答え。最も支持されている答えは、多くのc ++プログラマーが、型列挙型とスイッチケース(cの方法)を使用して、「アドホック」ポリモーフィズムに対する仮想関数の制限をまだ実現していないことを証明しています。virtualを使用することは、すっきりとして見えないかもしれませんが、それでも単一のディスパッチに制限されています。私の個人的な意見では、これはc ++の最大の欠陥です。
user3125280 2014年

@ user3125280私は4/5の記事とVisitorパターンのDesign Patternsの章を読みましたが、いずれも、このあいまいなパターンをcase stmtで使用することの利点を説明していません。少なくともそれを育てるためのThx!
spinkus 14

4
それはだ-私は、彼らはそれを説明しますかなり確信している@sam 同じ利点あなたがいることを常に入手サブクラス化/ランタイムポリモーフィズムからオーバーswitchswitch(クライアント側(コードの重複)で意思決定ハードコードおよび静的型チェックを提供していません。ケースの完全性や明瞭性などを確認してください)。ビジターパターンはタイプチェッカーによって検証され、通常はクライアントコードを単純化します。
Konrad Rudolph

@KonradRudolphありがとうございます。ただし、たとえば、PatternsやWikipediaの記事では明示的に取り上げられていません。私はあなたに同意しませんが、case stmtを使用することにも利点があるので、一般的には対照的ではない奇妙なことを主張できます。1。コレクションのオブジェクトにaccept()メソッドを必要としません。2.〜visitorは、不明なタイプのオブジェクトを処理できます。したがって、ケースstmtは、変更可能な型のコレクションが含まれるオブジェクト構造の操作に適しています。パターンは、訪問者パターンがそのようなシナリオ(p333)にはあまり適していないことを認めています。
spinkus 14

1
@SamPinkus konradのスポット-それvirtualが現代のプログラミング言語で機能が非常に便利な理由です-それらは拡張可能なプログラムの基本的なビルディングブロックです-私の意見では、Cの方法(選択した言語に応じてネストされたスイッチまたはパターンマッチなど)は拡張可能である必要のないコードがはるかにクリーンであり、証明者9のような複雑なソフトウェアでこのスタイルを目にして驚きました。さらに重要なことに、拡張性を提供したい言語は、再帰的な単一ディスパッチよりも優れたディスパッチパターンに対応する必要があります(つまり、ビジター)。
user3125280 2014

84

ここの全員が正しいですが、「いつ」に対処することはできません。まず、デザインパターンから:

訪問者を使用すると、操作する要素のクラスを変更せずに新しい操作を定義できます。

ここで、単純なクラス階層について考えてみましょう。クラス1、2、3、4とメソッドA、B、C、Dがあります。スプレッドシートのようにレイアウトします。クラスは行で、メソッドは列です。

現在、オブジェクト指向設計では、新しいメソッドよりも新しいクラスを成長させる可能性が高いと想定されているため、いわば行を追加する方が簡単です。新しいクラスを追加し、そのクラスの違いを指定し、残りを継承するだけです。

ただし、クラスが比較的静的な場合もありますが、メソッドを頻繁に追加する必要があります-列を追加します。OO設計の標準的な方法は、そのようなメソッドをすべてのクラスに追加することです。Visitorパターンはこれを簡単にします。

ちなみに、これはScalaのパターンマッチが解決しようとしている問題です。


なぜutlityクラスでビジターパターンを使用するのですか?次のようなユーティリティクラスを呼び出すことができます:AnalyticsManger.visit(someObjectToVisit)vs AnalyticsVisitor.visit(someOjbectToVisit)。違いは何ですか?彼らは両方とも懸念の分離を正しく行いますか?あなたが助けることができることを願っています。
j2emanue

@ j2emanue訪問者パターンは実行時に正しい訪問者のオーバーロードを使用するため。正しいオーバーロードを呼び出すには、コードで型キャストが必要です。
アクセス拒否

これで効率が向上しますか?私はそれがその良いアイデアをキャストすることを避けていると思います
j2emanue

@ j2emanueアイデアは、パフォーマンス上の理由ではなく、オープン/クローズの原則に準拠するコードを記述することです。アンクルボブbutunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
Access Denied

22

ビジターデザインパターンは、ディレクトリツリー、XML構造、またはドキュメントアウトラインのような「再帰的」構造のために本当によく働きます。

Visitorオブジェクトは、再帰構造の各ノード(各ディレクトリ、各XMLタグなど)を訪問します。Visitorオブジェクトは構造をループしません。代わりに、Visitorメソッドが構造の各ノードに適用されます。

これが典型的な再帰ノード構造です。ディレクトリまたはXMLタグの可能性があります。[もしあなたがJavaの人なら、子供たちのリストを構築して維持するための多くの追加メソッドを想像してみてください。]

class TreeNode( object ):
    def __init__( self, name, *children ):
        self.name= name
        self.children= children
    def visit( self, someVisitor ):
        someVisitor.arrivedAt( self )
        someVisitor.down()
        for c in self.children:
            c.visit( someVisitor )
        someVisitor.up()

このvisitメソッドは、構造内の各ノードにVisitorオブジェクトを適用します。この場合、それはトップダウンのビジターです。visitメソッドの構造を変更して、ボトムアップまたはその他の順序付けを行うことができます。

訪問者のためのスーパークラスです。visitメソッドで使用されます。構造内の各ノードに「到達」します。visitメソッドがupとを呼び出すため、downビジターは深度を追跡できます。

class Visitor( object ):
    def __init__( self ):
        self.depth= 0
    def down( self ):
        self.depth += 1
    def up( self ):
        self.depth -= 1
    def arrivedAt( self, aTreeNode ):
        print self.depth, aTreeNode.name

サブクラスは、各レベルでノードをカウントし、ノードのリストを蓄積して、適切なパス階層セクション番号を生成するなどの処理を実行できます。

これがアプリケーションです。ツリー構造を構築しますsomeTree。それは作成されますVisitordumpNodes

次にdumpNodes、をツリーに適用します。dumpNodeオブジェクトは、ツリー内の各ノードを「訪問」します。

someTree= TreeNode( "Top", TreeNode("c1"), TreeNode("c2"), TreeNode("c3") )
dumpNodes= Visitor()
someTree.visit( dumpNodes )

TreeNode visitアルゴリズムは、すべてのTreeNodeが訪問者のarrivedAtメソッドへの引数として使用されることを保証します。


8
他の人が述べたように、これは「階層的なビジターパターン」です。
PPC-Coder、

1
@ PPC-Coder「階層型ビジターパターン」とビジターパターンの違いは何ですか?
Tim Lovell-Smith

3
階層的なビジターパターンは、従来のビジターパターンよりも柔軟です。たとえば、階層パターンを使用すると、走査の深さを追跡し、どの分岐を通過するか、すべての走査を停止するかを決定できます。クラシックビジターはこの概念を持たず、すべてのノードを訪問します。
PPC-Coder 2015

18

これを見る1つの方法は、ビジターパターンは、クライアントが特定のクラス階層内のすべてのクラスにメソッドを追加できるようにする方法であるということです。

これは、かなり安定したクラス階層がある場合に役立ちますが、その階層で実行する必要がある要件が変化します。

古典的な例はコンパイラーなどです。抽象構文ツリー(AST)はプログラミング言語の構造を正確に定義できますが、ASTで実行する可能性のある操作は、プロジェクトの進行に伴って変化します(コードジェネレーター、プリティプリンター、デバッガー、複雑度メトリック分析)。

Visitorパターンがないと、開発者が新しい機能を追加するたびに、そのメソッドを基本クラスのすべての機能に追加する必要があります。これは、基本クラスが別のライブラリに表示される場合、または別のチームによって作成される場合は特に困難です。

(Visitorパターンは、データの操作をデータから遠ざけるため、適切なOOプラクティスと矛盾すると主張していると聞きました。Visitorパターンは、通常のOOプラクティスが失敗する状況で正確に役立ちます。)


また、次の点についても意見をお聞かせください。なぜutlityクラスだけでビジターパターンを使用するのですか。次のようなユーティリティクラスを呼び出すことができます:AnalyticsManger.visit(someObjectToVisit)vs AnalyticsVisitor.visit(someOjbectToVisit)。違いは何ですか?彼らは両方とも懸念の分離を正しく行いますか?あなたが助けることができることを願っています。
j2emanue

@ j2emanue:質問が理解できません。私はあなたがそれを具体化し、誰でも答えられるように完全な質問として投稿することをお勧めします。
2018

1
ここに新しい質問を投稿しました: stackoverflow.com/questions/52068876/…–
j2emanue

14

ビジターパターンを使用する理由は少なくとも3つあります。

  1. データ構造が変化してもわずかに異なるコードの急増を減らします。

  2. 計算を実装するコードを変更せずに、同じ計算を複数のデータ構造に適用します。

  3. レガシーコードを変更せずに、レガシーライブラリに情報を追加します。

これについて私が書いた記事をご覧ください。


1
私は、ビジターに見た中で最も大きな用途を1つ挙げて、あなたの記事にコメントしました。考え?
ジョージマウアー2013

13

コンラッド・ルドルフがすでに指摘したように、二重派遣が必要な場合に適しています

ここでは、二重ディスパッチが必要な状況と、ビジターがどのように支援してくれるかを示す例を示します。

例:

たとえば、iPhone、Android、Windows Mobileの3種類のモバイルデバイスがあるとします。

これら3つのデバイスにはすべて、Bluetooth無線がインストールされています。

ブルートゥースラジオが2つの別個のOEM – Intel&Broadcomからのものであると仮定しましょう。

例を私たちの議論に関連させるために、Intelラジオによって公開されるAPIがBroadcomラジオによって公開されるものとは異なると仮定しましょう。

これが私のクラスの外観です–

ここに画像の説明を入力してください ここに画像の説明を入力してください

ここで、モバイルデバイスのBluetoothをオンにする操作を紹介します。

関数のシグネチャは次のようになります–

 void SwitchOnBlueTooth(IMobileDevice mobileDevice, IBlueToothRadio blueToothRadio)

依存するので、デバイスの右のタイプBluetooth無線の右のタイプによっては、それがでオンにすることができ、適切な手順やアルゴリズムを呼び出します

原則として、これは3 x 2のマトリックスになり、関係するオブジェクトの適切なタイプに応じて、適切な操作をベクトル化しようとしています。

両方の引数のタイプに応じた多態的な動作。

ここに画像の説明を入力してください

これで、Visitorパターンをこの問題に適用できます。Wikipediaのページからインスピレーションを得ています– 「基本的に、ビジターはクラス自体を変更せずに、クラスのファミリーに新しい仮想関数を追加できます。代わりに、仮想関数の適切な特殊化のすべてを実装するビジタークラスを作成します。訪問者はインスタンス参照を入力として受け取り、二重ディスパッチによって目標を実装します。」

3x2マトリックスのため、ここでは二重ディスパッチが必要です

設定は次のようになります- ここに画像の説明を入力してください

私は別の質問に答える例を書きました。コードとその説明はここに記載されています


9

私はリンクをたどる方が簡単だとわかりました:

http://www.remondo.net/visitor-pattern-example-csharp/私は例を示しVisitorパターンの利点は何であることを示しているが、モック例ことを発見しました。ここには、次の異なるコンテナクラスがありPillます。

namespace DesignPatterns
{
    public class BlisterPack
    {
        // Pairs so x2
        public int TabletPairs { get; set; }
    }

    public class Bottle
    {
        // Unsigned
        public uint Items { get; set; }
    }

    public class Jar
    {
        // Signed
        public int Pieces { get; set; }
    }
}

上記のように、BilsterPackピルのペアが含まれているため、ペアの数に2を掛ける必要があります。また、データ型が異なり、キャストする必要があるBottleuseに気付く場合もunitあります。

したがって、メインメソッドでは、次のコードを使用して錠剤数を計算できます。

foreach (var item in packageList)
{
    if (item.GetType() == typeof (BlisterPack))
    {
        pillCount += ((BlisterPack) item).TabletPairs * 2;
    }
    else if (item.GetType() == typeof (Bottle))
    {
        pillCount += (int) ((Bottle) item).Items;
    }
    else if (item.GetType() == typeof (Jar))
    {
        pillCount += ((Jar) item).Pieces;
    }
}

上記のコードは違反していSingle Responsibility Principleます。つまり、新しいタイプのコンテナーを追加する場合は、メインメソッドコードを変更する必要があります。また、スイッチを長くすることは悪い習慣です。

したがって、次のコードを導入することにより:

public class PillCountVisitor : IVisitor
{
    public int Count { get; private set; }

    #region IVisitor Members

    public void Visit(BlisterPack blisterPack)
    {
        Count += blisterPack.TabletPairs * 2;
    }

    public void Visit(Bottle bottle)
    {
        Count += (int)bottle.Items;
    }

    public void Visit(Jar jar)
    {
        Count += jar.Pieces;
    }

    #endregion
}

Pillsの数をカウントする責任を呼び出されたクラスに移動しましたPillCountVisitor(そしてswitch caseステートメントを削除しました)。つまり、新しいタイプのピルコンテナーを追加する必要があるときはいつでも、PillCountVisitorクラスのみを変更する必要があります。また、IVisitorインターフェースは別のシナリオで使用するための一般的なものです。

AcceptメソッドをPillコンテナクラスに追加することにより:

public class BlisterPack : IAcceptor
{
    public int TabletPairs { get; set; }

    #region IAcceptor Members

    public void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }

    #endregion
}

私たちは訪問者が錠剤容器のクラスを訪問することを許可します。

最後に、次のコードを使用して錠剤数を計算します。

var visitor = new PillCountVisitor();

foreach (IAcceptor item in packageList)
{
    item.Accept(visitor);
}

つまり、すべての錠剤コンテナは、PillCountVisitor訪問者が錠剤の数を確認できるようにします。彼はあなたの錠剤を数える方法を知っています。

で、visitor.Count薬の価値を持っています。

http://butunclebob.com/ArticleS.UncleBob.IuseVisitorあなたが使うことができない現実のシナリオを参照多型シングル責任の原則に従うことを(返事を)。実際には:

public class HourlyEmployee extends Employee {
  public String reportQtdHoursAndPay() {
    //generate the line for this hourly employee
  }
}

このreportQtdHoursAndPay方法は報告と表現のためのものであり、これは単一責任の原則に違反しています。したがって、ビジターパターンを使用して問題を克服することをお勧めします。


2
こんにちは言った、あなたがあなたの答えを編集して、あなたが最も啓発的であると思った部分を追加してください。目標は知識データベースになることであり、リンクがダウンするため、SOは通常、リンクのみの回答を推奨しません。
ジョージマウアー2014

8

ダブルディスパッチは、このパターンを使用する理由の1つにすぎません。
ただし、単一のディスパッチパラダイムを使用する言語で二重以上のディスパッチを実装する単一の方法であることに注意してください。

パターンを使用する理由は次のとおりです。

1)操作は頻繁に変更されるため、モデルは頻繁に変更されないため、毎回モデルを変更せずに新しい操作を定義する必要があります

2)複数のアプリケーションで再利用可能なモデル使用したい、またはクライアントクラスが独自のクラスで動作を定義できるようにする拡張可能なモデルを使用したいので、モデルと動作を結び付けたくありません

3)モデルの具体的なタイプに依存する共通の操作がありますが、複数のクラスなどで複数の場所で共通のロジックを展開するため、各サブクラスにロジックを実装したくありません

4)ドメインモデルの設計を使用しており、同じ階層のモデルクラスが、他の場所に収集される可能性のある非常に多くの異なることを実行しています

5)二重発送が必要です。
インターフェース型で宣言された変数があり、実行時の型に従ってそれらを処理できるようにしたい…もちろんif (myObj instanceof Foo) {}、何のトリックも使わずに。
たとえば、特定の処理を適用するために、これらの変数をパラメーターとしてインターフェイスの具象型を宣言するメソッドに渡すことです。実行時に呼び出される選択はレシーバーの実行時のタイプにのみ依存するため、この方法は、言語をそのまま使用することは不可能であり、単一ディスパッチに依存します。
Javaでは、呼び出すメソッド(シグニチャー)はコンパイル時に選択され、ランタイムタイプではなく、宣言されたパラメーターのタイプに依存することに注意してください。

ビジターを使用する理由である最後の点も結果です。ビジターを実装すると(もちろん、複数のディスパッチをサポートしない言語の場合)、必ずダブルディスパッチの実装を導入する必要があるためです。

各要素にビジターを適用するための要素のトラバース(反復)は、パターンを使用する理由ではないことに注意してください。
モデルと処理を分割するため、パターンを使用します。
また、パターンを使用することで、イテレーター機能のメリットも得られます。
この機能は非常に強力で、一般的なメソッドと同様に、特定のメソッドを使用した一般的な型の反復を超えていますaccept()
これは特別な使用例です。だから私はそれを片側に置きます。


Javaの例

プレーヤーが駒の移動を要求したときに処理を定義するチェスの例を使用して、パターンの付加価値を説明します。

ビジターパターンを使用しない場合、ピースのサブクラスでピースの移動動作を直接定義できます。
たとえば、次のPieceようなインターフェースを持つことができます。

public interface Piece{

    boolean checkMoveValidity(Coordinates coord);

    void performMove(Coordinates coord);

    Piece computeIfKingCheck();

}

各Pieceサブクラスは次のように実装します。

public class Pawn implements Piece{

    @Override
    public boolean checkMoveValidity(Coordinates coord) {
        ...
    }

    @Override
    public void performMove(Coordinates coord) {
        ...
    }

    @Override
    public Piece computeIfKingCheck() {
        ...
    }

}

そして、すべてのピースのサブクラスについて同じことです。
この設計を説明する図クラスは次のとおりです。

【モデルクラス図】

このアプローチには、3つの重要な欠点があります。

-などの行動performMove()computeIfKingCheck()非常におそらく一般的なロジックを使用します。
例えば、どのような具体的にはPieceperformMove()最終的に特定の場所に現在のピースを設定し、潜在的に相手のピースを取るであろう。
関連する動作を収集するのではなく、複数のクラスに分割すると、何らかの方法で単一の責任パターンが無効になります。保守性を難しくします。

– サブクラスがcheckMoveValidity()認識Pieceまたは変更する可能性のある処理であってはならない処理。
人間やコンピュータのアクションを超えたチェックです。このチェックは、プレーヤーが要求した各アクションで実行され、要求された駒の移動が有効であることを確認します。
そのため、Pieceインターフェースでそれを提供したくありません。

–ボット開発者にとって困難なチェスゲームでは、通常、アプリケーションは標準API(Pieceインターフェース、サブクラス、ボード、一般的な動作など)を提供し、開発者がボット戦略を充実させることができます。
それを可能にするには、データと動作がPiece実装で密結合されていないモデルを提案する必要があります。

では、ビジターパターンを使いましょう!

2種類の構造があります。

–訪問を受け入れるモデルクラス(ピース)

–訪問者(移動操作)

以下は、パターンを示すクラス図です。

ここに画像の説明を入力してください

上部にはビジターがあり、下部にはモデルクラスがあります。

PieceMovingVisitorインターフェースは次のPieceとおりです(動作はの種類ごとに指定されています):

public interface PieceMovingVisitor {

    void visitPawn(Pawn pawn);

    void visitKing(King king);

    void visitQueen(Queen queen);

    void visitKnight(Knight knight);

    void visitRook(Rook rook);

    void visitBishop(Bishop bishop);

}

ピースが定義されました:

public interface Piece {

    void accept(PieceMovingVisitor pieceVisitor);

    Coordinates getCoordinates();

    void setCoordinates(Coordinates coordinates);

}

その主な方法は次のとおりです。

void accept(PieceMovingVisitor pieceVisitor);

これは最初のディスパッチ、つまりPieceレシーバーに基づく呼び出しを提供します。
コンパイル時に、メソッドはaccept()Pieceインターフェースのメソッドにバインドされ、実行時に、バインドされたメソッドがランタイムPieceクラスで呼び出されます。
そしてaccept()、2番目のディスパッチを実行するのはメソッド実装です。

実際、オブジェクトPieceがアクセスしたい各サブクラスPieceMovingVisitorは、PieceMovingVisitor.visit()引数自体を渡すことでメソッドを呼び出します。
このようにして、コンパイラーは、コンパイル時と同じように、宣言されたパラメーターの型を具象型に制限します。
2回目の発送があります。
これはBishopそれを説明するサブクラスです:

public class Bishop implements Piece {

    private Coordinates coord;

    public Bishop(Coordinates coord) {
        super(coord);
    }

    @Override
    public void accept(PieceMovingVisitor pieceVisitor) {
        pieceVisitor.visitBishop(this);
    }

    @Override
    public Coordinates getCoordinates() {
        return coordinates;
    }

   @Override
    public void setCoordinates(Coordinates coordinates) {
        this.coordinates = coordinates;
   }

}

そしてここに使用例:

// 1. Player requests a move for a specific piece
Piece piece = selectPiece();
Coordinates coord = selectCoordinates();

// 2. We check with MoveCheckingVisitor that the request is valid
final MoveCheckingVisitor moveCheckingVisitor = new MoveCheckingVisitor(coord);
piece.accept(moveCheckingVisitor);

// 3. If the move is valid, MovePerformingVisitor performs the move
if (moveCheckingVisitor.isValid()) {
    piece.accept(new MovePerformingVisitor(coord));
}

訪問者の欠点

Visitorパターンは非常に強力なパターンですが、使用する前に考慮すべき重要な制限がいくつかあります。

1)カプセル化を減らす/壊すリスク

一部の種類の操作では、ビジターパターンによってドメインオブジェクトのカプセル化が低下または破壊される場合があります。

たとえば、MovePerformingVisitor クラスは実際のピースの座標を設定する必要があるため、Pieceインターフェイスはそれを行う方法を提供する必要があります。

void setCoordinates(Coordinates coordinates);

Piece座標変更の責任は、Pieceサブクラス以外の他のクラスに開放されています。
ビジターが実行した処理をPieceサブクラスに移動することもできません。はビジターの実装を受け入れる
ため、実際には別の問題が発生しPiece.accept()ます。ビジターが何を実行するのかわからないため、ピースの状態を変更するかどうか、またどのように変更するかについてはわかりません。
ビジターを識別する方法Piece.accept()は、ビジターの実装に従って後処理を実行することです。これは、Visitor実装とPieceサブクラスの間に高度な結合を作成するため、非常に悪い考えです。また、おそらくトリックとしてgetClass()instanceofまたはVisitor実装を識別するマーカーを使用する必要があります。

2)モデル変更の要件

Decoratorたとえば、他のいくつかの動作デザインパターンとは異なり、訪問者パターンは侵入型です。
実際accept()に、訪問することを受け入れるメソッドを提供するために、初期レシーバークラスを変更する必要があります。
私たちは、のためにすべての問題を持っていなかったPieceし、これらのようなそのサブクラスがある私たちのクラス
組み込みまたはサードパーティのクラスでは、物事はそれほど簡単ではありません。メソッド
を追加するには、それらをラップまたは継承する(可能な場合)必要がありaccept()ます。

3)インダイレクション

パターンは複数の間接参照を作成します。
二重ディスパッチとは、単一の呼び出しではなく、2つの呼び出しを意味します。

call the visited (piece) -> that calls the visitor (pieceMovingVisitor)

そして、訪問者が訪問したオブジェクトの状態を変更するときに、追加の間接参照を持つことができます。
サイクルのように見えるかもしれません:

call the visited (piece) -> that calls the visitor (pieceMovingVisitor) -> that calls the visited (piece)

6

Cay Horstmannは、OOデザインとパターンの本でVisitorをどこに適用するかを示す良い例を持っています。彼は問題を要約します:

複合オブジェクトは、多くの場合、個々の要素で構成される複雑な構造を持っています。一部の要素には、子要素が含まれる場合があります。...要素に対する操作は、その子要素を訪問し、それらに子操作を適用して、結果を結合します。...しかし、そのような設計に新しい操作を追加することは容易ではありません。

簡単ではない理由は、構造クラス自体に操作が追加されるためです。たとえば、ファイルシステムがあるとします。

FileSystemクラス図

この構造で実装する可能性のあるいくつかの操作(機能)を次に示します。

  • ノード要素の名前を表示する(ファイルリスト)
  • ノード要素の計算されたサイズを表示します(ディレクトリのサイズにはすべての子要素のサイズが含まれます)

FileSystemの各クラスに関数を追加して、操作を実装することができます(方法は非常に明白であるため、以前はこれを行っていました)。問題は、新しい機能(上記の「etc.」行)を追加するたびに、構造クラスにメソッドを追加しなければならない可能性があることです。ある時点で、ソフトウェアにいくつかの操作を追加した後、それらのクラスのメソッドは、クラスの機能的結合の観点からもはや意味をなさなくなります。たとえば、ファイルシステムに最新の視覚化機能を実装するためのFileNodeメソッドを持つがあるcalculateFileColorForFunctionABC()とします。

ビジターパターン(多くのデザインパターンと同様)は、多くの変更を必要とせずにコードを変更できるより良い方法があり、優れたデザイン原則(高い凝集性、低い結合)を尊重する開発者の苦痛と苦痛から生まれました。)。あなたがその痛みを感じるまで、多くのパターンの有用性を理解するのは難しいと私は思います。痛みを説明することは(追加された「etc.」機能を使用して上記のようにしようとすることですが)、説明のスペースを占め、注意散漫になります。このため、パターンを理解することは困難です。

ビジターを使用すると、データ構造(たとえばFileSystemNodes)の機能をデータ構造自体から切り離すことができます。このパターンにより、設計はまとまりを尊重することができます。データ構造クラスはより単純で(メソッドの数は少ない)、機能はVisitor実装にカプセル化されます。これは、二重ディスパッチ(パターンの複雑な部分)を介して行われaccept()ます。構造クラスのvisitX()メソッドとビジター(機能)クラスのメソッドを使用します。

訪問者が適用されたFileSystemクラス図

この構造により、(構造クラスを変更せずに)具象ビジターとして構造に作用する新しい機能を追加できます。

訪問者が適用されたFileSystemクラス図

たとえばPrintNameVisitor、ディレクトリリスト機能PrintSizeVisitorを実装すると、サイズ付きのバージョンを実装するです。XMLでデータを生成する「ExportXMLVisitor」、またはJSONなどでデータを生成する別のビジターがいると想像できます。DOTなどのグラフィカル言語を使用してディレクトリツリーを表示するビジターを視覚化することもできます別のプログラムで。

最後に、ダブルディスパッチによるビジターの複雑さは、理解、コーディング、デバッグが困難であることを意味します。要するに、それは高いオタク要素を持ち、KISS原理に逆行します。研究者が行った調査では、Visitorは物議を醸しているパターンであることが示されていました(その有用性についてコンセンサスはありませんでした)。いくつかの実験は、それがコードを維持することをより簡単にしないことさえ示しました。


私が考えるディレクトリ構造は良い複合パターンですが、最後の段落に同意します。
zar

5

私の意見では、新しい操作を追加するための作業量はVisitor Pattern、各要素の構造を使用するか直接変更することで、ほぼ同じです。また、たとえばCow、新しい要素クラスを追加すると、Operationインターフェースが影響を受け、これがすべての既存の要素クラスに伝播するため、すべての要素クラスの再コンパイルが必要になります。だからポイントは何ですか?


4
訪問者を使用するほとんどの場合は、オブジェクト階層のトラバースを操作しているときです。ネストされたツリーメニューについて考えます。すべてのノードを折りたたみます。ビジターを実装しない場合は、グラフ走査コードを記述する必要があります。または、訪問者と一緒に:rootElement.visit (node) -> node.collapse()。ビジターを使用すると、各ノードはそのすべての子に対してグラフ走査を実装するので、完了です。
George Mauer 2013年

@GeorgeMauer、ダブルディスパッチのコンセプトは、私にとってモチベーションを明確にしました。タイプに依存するロジックは、タイプまたは痛みの世界のどちらかです。トラバーサルロジックを分散するという考えには、まだ一時停止があります。より効率的ですか?それはより保守可能ですか?「fold to level N」が要件として追加された場合はどうなりますか?
nik.shornikov 2015

@ nik.shornikovの効率は、ここでは特に問題になりません。ほとんどすべての言語で、いくつかの関数呼び出しは無視できるオーバーヘッドです。それ以上のものはミクロ最適化です。それはより保守可能ですか?まあ、それは異なります。ほとんどの場合そうだと思いますが、そうでないこともあります。「折りたたみレベルN」も。levelsRemainingパラメータとしてカウンタを簡単に渡す。次のレベルの子供を呼び出す前にそれを減らします。あなたの訪問者の内部if(levelsRemaining == 0) return
George Mauer

1
@GeorgeMauer、効率が軽微な問題であることで完全に合意。しかし、たとえば署名の受け入れのオーバーライドなどの保守性は、決定を煮詰めるべきだと私が思うところです。
nik.shornikov 2015

5

アスペクトオブジェクトプログラミングと同じアンダーグラウンド実装としての訪問者パターン。

たとえば、操作する要素のクラスを変更せずに新しい操作を定義した場合


アスペクトオブジェクトプログラミングについて言及するために
マイルマ '

5

ビジターパターンの簡単な説明。変更が必要なクラスはすべて「accept」メソッドを実装する必要があります。クライアントは、このacceptメソッドを呼び出して、そのクラスのファミリーでいくつかの新しいアクションを実行し、それによって機能を拡張します。クライアントは、この1つのacceptメソッドを使用して、特定のアクションごとに異なるビジタークラスを渡すことにより、幅広い新しいアクションを実行できます。訪問者クラスには、ファミリ内のすべてのクラスに対して同じ特定のアクションを達成する方法を定義する、複数のオーバーライドされた訪問メソッドが含まれています。これらのvisitメソッドには、動作するインスタンスが渡されます。

使用を検討する可能性がある場合

  1. クラスファミリがある場合、多くの新しいアクションをすべて追加する必要があることはわかっていますが、何らかの理由で、将来、クラスファミリを変更または再コンパイルすることができません。
  2. 新しいアクションを追加し、その新しいアクションを複数のクラスに分散するのではなく、1つのビジタークラス内で完全に定義する場合。
  3. 上司が、今すぐ何かをしなければならない一連のクラスを作成する必要があると言ったとき...!

4

ボブおじさんの記事を見つけてコメントを読むまでは、このパターンを理解していませんでした。次のコードを検討してください。

public class Employee
{
}

public class SalariedEmployee : Employee
{
}

public class HourlyEmployee : Employee
{
}

public class QtdHoursAndPayReport
{
    public void PrintReport()
    {
        var employees = new List<Employee>
        {
            new SalariedEmployee(),
            new HourlyEmployee()
        };
        foreach (Employee e in employees)
        {
            if (e is HourlyEmployee he)
                PrintReportLine(he);
            if (e is SalariedEmployee se)
                PrintReportLine(se);
        }
    }

    public void PrintReportLine(HourlyEmployee he)
    {
        System.Diagnostics.Debug.WriteLine("hours");
    }
    public void PrintReportLine(SalariedEmployee se)
    {
        System.Diagnostics.Debug.WriteLine("fix");
    }
}

class Program
{
    static void Main(string[] args)
    {
        new QtdHoursAndPayReport().PrintReport();
    }
}

単一の責任であることが確認されているため、見た目は良いかもしれませんが、オープン/クローズの原則に違反しています。型チェックを行う場合は、新しいEmployeeタイプを作成するたびに追加する必要があります。そして、あなたが知らないなら、コンパイル時にそれを決して知ることはないでしょう。

ビジターパターンを使用すると、オープン/クローズの原則に違反せず、単一の責任に違反しないため、コードをよりクリーンにすることができます。また、visitの実装を忘れた場合はコンパイルされません。

public abstract class Employee
{
    public abstract void Accept(EmployeeVisitor v);
}

public class SalariedEmployee : Employee
{
    public override void Accept(EmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public class HourlyEmployee:Employee
{
    public override void Accept(EmployeeVisitor v)
    {
        v.Visit(this);
    }
}

public interface EmployeeVisitor
{
    void Visit(HourlyEmployee he);
    void Visit(SalariedEmployee se);
}

public class QtdHoursAndPayReport : EmployeeVisitor
{
    public void Visit(HourlyEmployee he)
    {
        System.Diagnostics.Debug.WriteLine("hourly");
        // generate the line of the report.
    }
    public void Visit(SalariedEmployee se)
    {
        System.Diagnostics.Debug.WriteLine("fix");
    } // do nothing

    public void PrintReport()
    {
        var employees = new List<Employee>
        {
            new SalariedEmployee(),
            new HourlyEmployee()
        };
        QtdHoursAndPayReport v = new QtdHoursAndPayReport();
        foreach (var emp in employees)
        {
            emp.Accept(v);
        }
    }
}

class Program
{

    public static void Main(string[] args)
    {
        new QtdHoursAndPayReport().PrintReport();
    }       
}  
}

マジックはv.Visit(this)同じように見えますが、ビジターの異なるオーバーロードを呼び出すため、実際には異なります。


うん、フラットリストだけでなく、ツリー構造を扱うときに特に便利だと思います(フラットリストはツリーの特殊なケースになります)。お気づきのように、リストだけではひどく面倒ではありませんが、ノード間のナビゲーションがより複雑になるため、ビジターは救世主になる可能性があります
George Mauer

3

@Federico A. Ramponiの優れた回答に基づいています。

この階層があると想像してください:

public interface IAnimal
{
    void DoSound();
}

public class Dog : IAnimal
{
    public void DoSound()
    {
        Console.WriteLine("Woof");
    }
}

public class Cat : IAnimal
{
    public void DoSound(IOperation o)
    {
        Console.WriteLine("Meaw");
    }
}

ここに「ウォーク」メソッドを追加する必要がある場合はどうなりますか?それはデザイン全体にとって苦痛です。

同時に、「ウォーク」メソッドを追加すると、新しい質問が生成されます。「食べる」や「寝る」はどうですか?追加するすべての新しいアクションまたは操作に対して、Animal階層に新しいメソッドを本当に追加する必要がありますか?これは醜く、最も重要なことです。どうすれば、Animalインターフェースを閉じることができなくなります。したがって、ビジターパターンを使用すると、階層を変更せずに新しいメソッドを階層に追加できます。

したがって、次のC#の例を確認して実行してください。

using System;
using System.Collections.Generic;

namespace VisitorPattern
{
    class Program
    {
        static void Main(string[] args)
        {
            var animals = new List<IAnimal>
            {
                new Cat(), new Cat(), new Dog(), new Cat(), 
                new Dog(), new Dog(), new Cat(), new Dog()
            };

            foreach (var animal in animals)
            {
                animal.DoOperation(new Walk());
                animal.DoOperation(new Sound());
            }

            Console.ReadLine();
        }
    }

    public interface IOperation
    {
        void PerformOperation(Dog dog);
        void PerformOperation(Cat cat);
    }

    public class Walk : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Dog walking");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Cat Walking");
        }
    }

    public class Sound : IOperation
    {
        public void PerformOperation(Dog dog)
        {
            Console.WriteLine("Woof");
        }

        public void PerformOperation(Cat cat)
        {
            Console.WriteLine("Meaw");
        }
    }

    public interface IAnimal
    {
        void DoOperation(IOperation o);
    }

    public class Dog : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }

    public class Cat : IAnimal
    {
        public void DoOperation(IOperation o)
        {
            o.PerformOperation(this);
        }
    }
}

散歩は、両方に彼らしている共通以来、適切な例ではありません食べるのDogと同様にCat。それらを継承するか、適切な例を選択するために、それらを基本クラスで作成することもできます。
Abhinav Gauniyal

サウンドは違いますが、良いサンプルですが、ビジターパターンと関係があるかどうかは不明です
DAG

3

ビジター

訪問者は、クラス自体を変更することなく、クラスのファミリーに新しい仮想関数を追加できます。代わりに、仮想関数の適切な特殊化のすべてを実装するビジタークラスを作成します

訪問者の構造:

ここに画像の説明を入力してください

次の場合は、訪問者パターンを使用します。

  1. 構造にグループ化された異なるタイプのオブジェクトに対して同様の操作を実行する必要があります
  2. あなたは多くの明確で無関係な操作を実行する必要があります。操作をオブジェクトから分離します
  3. オブジェクト構造を変更せずに新しい操作を追加する必要があります
  4. クラスの変更や派生を強制するのではなく、関連する操作を1つのクラスにまとめます。
  5. ソースがないか、ソースを変更できないクラスライブラリに関数を追加する

にもかかわらずビジターパターンは、オブジェクトの既存のコードを変更せずに新しい操作を追加するための柔軟性を提供し、この柔軟性は欠点が付属しています。

新しいVisitableオブジェクトが追加されている場合は、VisitorおよびConcreteVisitorクラスのコードを変更する必要があります。この問題に対処する回避策があります:リフレクションを使用してください。これはパフォーマンスに影響を与えます。

コードスニペット:

import java.util.HashMap;

interface Visitable{
    void accept(Visitor visitor);
}

interface Visitor{
    void logGameStatistics(Chess chess);
    void logGameStatistics(Checkers checkers);
    void logGameStatistics(Ludo ludo);    
}
class GameVisitor implements Visitor{
    public void logGameStatistics(Chess chess){
        System.out.println("Logging Chess statistics: Game Completion duration, number of moves etc..");    
    }
    public void logGameStatistics(Checkers checkers){
        System.out.println("Logging Checkers statistics: Game Completion duration, remaining coins of loser");    
    }
    public void logGameStatistics(Ludo ludo){
        System.out.println("Logging Ludo statistics: Game Completion duration, remaining coins of loser");    
    }
}

abstract class Game{
    // Add game related attributes and methods here
    public Game(){

    }
    public void getNextMove(){};
    public void makeNextMove(){}
    public abstract String getName();
}
class Chess extends Game implements Visitable{
    public String getName(){
        return Chess.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Checkers extends Game implements Visitable{
    public String getName(){
        return Checkers.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}
class Ludo extends Game implements Visitable{
    public String getName(){
        return Ludo.class.getName();
    }
    public void accept(Visitor visitor){
        visitor.logGameStatistics(this);
    }
}

public class VisitorPattern{
    public static void main(String args[]){
        Visitor visitor = new GameVisitor();
        Visitable games[] = { new Chess(),new Checkers(), new Ludo()};
        for (Visitable v : games){
            v.accept(visitor);
        }
    }
}

説明:

  1. VisitableElement)はインターフェースであり、このインターフェースメソッドをクラスのセットに追加する必要があります。
  2. VisitorVisitable要素の操作を実行するメソッドを含むインターフェースです。
  3. GameVisitorVisitorインターフェース(ConcreteVisitor)を実装するクラスです。
  4. Visitable要素はVisitor、関連するVisitorインターフェースのメソッドを受け入れて呼び出します。
  5. あなたは扱うことができるGameようElementようなゲームやコンクリートChess,Checkers and LudoなどConcreteElements

上記の例でChess, Checkers and Ludoは、3つの異なるゲーム(およびVisitableクラス)があります。ある晴れた日に、各ゲームの統計を記録するシナリオに遭遇しました。したがって、個々のクラスを変更して統計機能を実装しなくても、その責任をGameVisitorクラスに集中させることができます。これにより、各ゲームの構造を変更せずにトリックを実行できます。

出力:

Logging Chess statistics: Game Completion duration, number of moves etc..
Logging Checkers statistics: Game Completion duration, remaining coins of loser
Logging Ludo statistics: Game Completion duration, remaining coins of loser

参照する

oodesign記事

ソースメイキング記事

詳細については

デコレータ

パターンを使用すると、同じクラスの他のオブジェクトの動作に影響を与えることなく、静的または動的に個々のオブジェクトに動作を追加できます

関連記事:

IOのデコレータパターン

デコレータパターンを使用する場合


2

http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Visitor.htmlの説明と例が本当に気に入っています。

前提は、固定されたプライマリクラス階層があることです。おそらくそれは別のベンダーからのものであり、その階層を変更することはできません。ただし、その意図は、その階層に新しいポリモーフィックメソッドを追加することです。つまり、通常は基本クラスインターフェイスに何かを追加する必要があります。したがって、ジレンマは、基本クラスにメソッドを追加する必要があるということですが、基本クラスに触れることはできません。これをどうやって回避しますか?

この種の問題を解決する設計パターンは「ビジター」(「設計パターン」ブックの最後の1つ)と呼ばれ、前のセクションで示した二重ディスパッチスキームに基づいています。

訪問者パターンを使用すると、タイプVisitorの個別のクラス階層を作成して、主タイプで実行される操作を仮想化することにより、主タイプのインターフェースを拡張できます。プライマリタイプのオブジェクトは、ビジターを単に「受け入れ」、ビジターの動的にバインドされたメンバー関数を呼び出します。


技術的にはVisitorパターンですが、これは実際には彼らの例からの基本的なダブルディスパッチです。これだけでは特に有用性は見えません。
George Mauer

1

いつ、どのようにして理解したのか、その理由を理解したことがありません。C ++のような言語の背景を持つ人を助ける場合は、これを非常に注意深く読んでください。

「仮想関数がC ++で動的にディスパッチされる間、関数のオーバーロードは静的に行われる」ため、遅延についてはビジターパターンを使用します。

または、別の言い方をすると、ApolloSpacecraftオブジェクトに実際にバインドされているSpaceShip参照を渡すときにCollideWith(ApolloSpacecraft&)が確実に呼び出されるようにします。

class SpaceShip {};
class ApolloSpacecraft : public SpaceShip {};
class ExplodingAsteroid : public Asteroid {
public:
  virtual void CollideWith(SpaceShip&) {
    cout << "ExplodingAsteroid hit a SpaceShip" << endl;
  }
  virtual void CollideWith(ApolloSpacecraft&) {
    cout << "ExplodingAsteroid hit an ApolloSpacecraft" << endl;
  }
}

2
訪問者パターンで動的ディスパッチを使用すると、私は完全に困惑します。パターンの推奨される使用法は、コンパイル時に実行できる分岐について説明しています。これらのケースは、関数テンプレートを使用する方が良いようです。
Praxeolitic 2014

0

@Federico A. Ramponiの素晴らしい説明をありがとう、私はちょうどこれをJavaバージョンで作成しました。お役に立てれば幸いです。

また、同じように@Konradルドルフが指摘し、それが実際だダブルディスパッチ使用して2つのランタイムメソッドを決定するために一緒に、具体的な事例を。

そのため、実際に操作インターフェイスが適切に定義されている限り、操作エグゼキュータに共通のインターフェイスを作成する必要はありません。

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showTheHobby(food);
        Katherine katherine = new Katherine();
        katherine.presentHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void embed(Katherine katherine);
}


class Hearen {
    String name = "Hearen";
    void showTheHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine {
    String name = "Katherine";
    void presentHobby(Hobby hobby) {
        hobby.embed(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void embed(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}

ご想像のとおり、このパターンでは本質的に重要な部分ではありませんが、共通のインターフェイスを使用するとわかりやすくなります。

import static java.lang.System.out;
public class Visitor_2 {
    public static void main(String...args) {
        Hearen hearen = new Hearen();
        FoodImpl food = new FoodImpl();
        hearen.showHobby(food);
        Katherine katherine = new Katherine();
        katherine.showHobby(food);
    }
}

interface Hobby {
    void insert(Hearen hearen);
    void insert(Katherine katherine);
}

abstract class Person {
    String name;
    protected Person(String n) {
        this.name = n;
    }
    abstract void showHobby(Hobby hobby);
}

class Hearen extends  Person {
    public Hearen() {
        super("Hearen");
    }
    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class Katherine extends Person {
    public Katherine() {
        super("Katherine");
    }

    @Override
    void showHobby(Hobby hobby) {
        hobby.insert(this);
    }
}

class FoodImpl implements Hobby {
    public void insert(Hearen hearen) {
        out.println(hearen.name + " start to eat bread");
    }
    public void insert(Katherine katherine) {
        out.println(katherine.name + " start to eat mango");
    }
}

0

あなたの質問はいつ知るかです:

最初にビジターパターンでコードを記述しません。私は標準をコーディングし、必要が生じるのを待ってからリファクタリングします。したがって、一度に1つずつインストールした複数の支払いシステムがあるとします。チェックアウト時に、たとえば次のような多くのif条件(またはinstanceOf)が存在する可能性があります。

//psuedo code
    if(payPal) 
    do paypal checkout 
    if(stripe)
    do strip stuff checkout
    if(payoneer)
    do payoneer checkout

今、私が10種類の支払い方法を持っていると想像してみてください。そのため、そのようなパターンが発生するのを見ると、ビジターがそのすべてを分離するのに役立ち、その後、次のようなものを呼び出すことになります。

new PaymentCheckoutVistor(paymentType).visit()

実装例は、ユースケースを示すだけで、ここにある多くの例から実装方法を確認できます。

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