訪問者パターン
ビジターパターンは、アルゴリズムをオブジェクト構造から分離するソフトウェア設計パターンです。この分離により、既存のオブジェクト構造を変更することなく、新しい操作を追加できます。これは、オブジェクト指向プログラミングとソフトウェア エンジニアリングにおけるオープン/クローズ原則に従う 1 つの方法です。
本質的には、ビジターはクラスを変更することなく、クラスのファミリーに新しい仮想関数を追加できるようにします。代わりに、仮想関数の適切な特殊化をすべて実装するビジター クラスが作成されます。ビジターはインスタンス参照を入力として受け取り、ダブル ディスパッチを通じて目標を実装します。
合計型とパターン マッチングを備えたプログラミング言語では、ビジター クラスがオブジェクトの型で簡単に分岐し、ビジターがまだ処理していない新しいオブジェクト型が定義された場合にコンパイラ エラーを生成できるため、ビジター パターンの利点の多くが失われます。
概要
Visitor [1] デザインパターンは、柔軟で再利用可能なオブジェクト指向ソフトウェア、つまり実装、変更、テスト、再利用が容易なオブジェクトを設計するために、繰り返し発生する設計上の問題を解決する方法を説明した、よく知られている23のGang of Fourデザインパターンの 1つです。
Visitor デザイン パターンはどのような問題を解決できますか?
- クラスを変更せずに、オブジェクト構造の (一部の) クラスに対して新しい操作を定義できる必要があります。
新しい操作が頻繁に必要になり、オブジェクト構造が多数の無関係なクラスで構成されている場合、新しい操作が必要になるたびに新しいサブクラスを追加するのは柔軟性に欠けます。「[...] これらすべての操作をさまざまなノード クラスに分散すると、理解、保守、変更が困難なシステムになります。」[1]
Visitor デザイン パターンはどのようなソリューションを説明していますか?
- オブジェクト構造の要素に対して実行される操作を実装する別の (ビジター) オブジェクトを定義します。
- クライアントはオブジェクト構造をトラバースし、要素に対してディスパッチ操作 accept (visitor)を呼び出します。これにより、リクエストが「受け入れられたビジター オブジェクト」に「ディスパッチ」(委任) されます。次に、ビジター オブジェクトは要素に対して操作を実行します (「要素を訪問」します)。
これにより、新しいビジター オブジェクトを追加することで、オブジェクト構造のクラスから独立して新しい操作を作成できるようになります。
下記の UML クラス図とシーケンス図も参照してください。
意味
ギャング・オブ・フォーは訪問者を次のように定義しています。
オブジェクト構造の要素に対して実行される操作を表します。Visitor を使用すると、操作対象の要素のクラスを変更せずに新しい操作を定義できます。
Visitor の性質上、パブリック API にプラグインするのに理想的なパターンとなり、クライアントはソースを変更することなく、「visiting」クラスを使用してクラスに対して操作を実行できるようになります。[2]
利点
ビジタークラスへのオペレーションの移行は、次のような場合に有益です。
- オブジェクト構造に対して多くの無関係な操作が必要となる。
- オブジェクト構造を構成するクラスは既知であり、変更されることは予想されない。
- 新しい操作を頻繁に追加する必要がある。
- アルゴリズムにはオブジェクト構造の複数のクラスが含まれますが、それを1つの場所で管理することが望まれます。
- アルゴリズムは複数の独立したクラス階層にわたって機能する必要があります。
visit
ただし、このパターンの欠点は、新しいクラスでは通常、各ビジターに新しいメソッドを追加する
必要があるため、クラス階層の拡張がより困難になることです。
応用
2Dコンピュータ支援設計(CAD) システムの設計について考えてみましょう。その中心には、円、線、円弧などの基本的な幾何学的形状を表す複数のタイプがあります。エンティティはレイヤーに整理され、タイプ階層の最上位には、レイヤーのリストといくつかの追加プロパティからなる図面があります。
この型階層における基本的な操作は、図面をシステムのネイティブ ファイル形式で保存することです。一見すると、階層内のすべての型にローカル保存メソッドを追加しても問題ないように思えるかもしれません。しかし、図面を他のファイル形式で保存できることも便利です。さまざまなファイル形式で保存するためのメソッドをさらに追加すると、比較的純粋な元のジオメトリ データ構造がすぐに乱雑になります。
これを解決する単純な方法は、ファイル形式ごとに別々の関数を維持することです。このような保存関数は、図面を入力として受け取り、それを走査し、その特定のファイル形式にエンコードします。これは、追加された異なる形式ごとに行われるため、関数間の重複が蓄積されます。たとえば、ラスター形式で円形を保存するには、特定のラスター形式が何であるかに関係なく、非常によく似たコードが必要であり、他のプリミティブ形状とは異なります。線やポリゴンなどの他のプリミティブ形状の場合も同様です。したがって、コードはオブジェクトを走査する大きな外側のループになり、ループ内にはオブジェクトのタイプを照会する大きな決定木があります。このアプローチのもう 1 つの問題は、1 つ以上のセーバーで形状が見落とされやすいこと、または新しいプリミティブ形状が導入されても、保存ルーチンが 1 つのファイル タイプに対してのみ実装され、他のファイル タイプに対しては実装されないことです。これにより、コード拡張とメンテナンスの問題が発生します。同じファイルのバージョンが増えるにつれて、メンテナンスが複雑になります。
代わりに、ビジター パターンを適用できます。これは、階層全体に対する論理操作 (つまり save(image_tree)) を 1 つのクラス (つまり Saver) にエンコードします。このクラスは、ツリーをトラバースするための共通メソッドを実装し、フォーマット固有の動作のために実装される仮想ヘルパー メソッド (つまり save_circle、save_square など) を記述します。CAD の例の場合、このようなフォーマット固有の動作は、Visitor のサブクラス (つまり SaverPNG) によって実装されます。そのため、型チェックとトラバーサル手順の重複はすべて削除されます。さらに、共通ベースのトラバーサル/保存関数によってシェイプが期待されるようになったため、シェイプが省略されるとコンパイラーがエラーを発するようになりました。
反復ループ
ビジターパターンは、イテレータパターンと同様にコンテナのようなデータ構造の反復処理に使用できますが、機能が制限されています。[3] : 288 たとえば、ディレクトリ構造の反復処理は、より一般的なループパターンではなく関数クラスで実装できます。これにより、反復コードを再利用しながら、すべての項目にビジター機能を実装することで、ディレクトリの内容からさまざまな有用な情報を取得できます。これは Smalltalk システムで広く採用されており、C++ でも使用されています。[3] : 289 ただし、このアプローチの欠点は、ループから簡単に抜け出したり、同時に反復処理 (並列、つまり単一の変数で同時に 2 つのコンテナを走査) したりできないことです。 [3] : 289後者の場合、ビジターがこれらの機能をサポートするために追加の機能を記述する必要があります。[3 ] : 289 i
構造
UML クラス図とシーケンス図
上記のUML クラス図では、ElementA
クラスは新しい操作を直接実装していません。代わりに、要求を「受け入れられたビジター オブジェクト」 ( )に「ディスパッチ」(委任) するディスパッチ操作をElementA
実装します。クラスは操作 ( )
を実装します。次に、にディスパッチすることで を実装します。クラスは操作 ( ) を実装します。
accept(visitor)
visitor.visitElementA(this)
Visitor1
visitElementA(e:ElementA)
ElementB
accept(visitor)
visitor.visitElementB(this)
Visitor1
visitElementB(e:ElementB)
UMLシーケンス図は、実行
時の相互作用を示しています。オブジェクトは、オブジェクト構造 ( ) の要素をトラバースし、各要素を
呼び出します。
最初に、 はを
呼び出し、これが受け入れられたオブジェクトを呼び出します。要素自体 ( ) は に渡され、 が「訪問」 ( を呼び出す)
できるようになります。
その後、 はを
呼び出し、これが を呼び出し、これが「訪問」( を呼び出す)します。
Client
ElementA,ElementB
accept(visitor)
Client
accept(visitor)
ElementA
visitElementA(this)
visitor
this
visitor
ElementA
operationA()
Client
accept(visitor)
ElementB
visitElementB(this)
visitor
ElementB
operationB()
クラス図
詳細
ビジター パターンでは、一般的なオブジェクト指向言語 ( C++、Java、Smalltalk、Objective-C、Swift、JavaScript、Python、C#など) と同様に、シングル ディスパッチをサポートするプログラミング言語が必要です。この条件下で、それぞれが何らかのクラス型である 2 つのオブジェクトを考えます。1 つは要素と呼ばれ、もう 1 つはビジターと呼ばれます。
ビジターは、visit
要素の各クラスに対して、要素を引数として受け取るメソッドを宣言します。具体的なビジターはビジター クラスから派生し、これらのvisit
メソッドを実装します。各メソッドは、オブジェクト構造で動作するアルゴリズムの一部を実装します。アルゴリズムの状態は、具体的なビジター クラスによってローカルに維持されます。
要素は、accept
ビジターを引数として受け取り、ビジターを受け入れるメソッドを宣言します。要素クラスから派生した具象要素はaccept
、メソッドを実装します。最も単純な形式では、これはビジターのメソッドの呼び出しにすぎませんvisit
。子オブジェクトのリストを保持する複合accept
要素は、通常、これらを反復処理して、各子のメソッドを呼び出します。
クライアントは、直接的または間接的にオブジェクト構造を作成し、具体的なビジターをインスタンス化します。Visitor パターンを使用して実装された操作を実行する場合、クライアントはaccept
最上位の要素のメソッドを呼び出します。
プログラム内でメソッドが呼び出されるとaccept
、その実装は要素の動的型とビジターの静的型の両方に基づいて選択されます。関連付けられたvisit
メソッドが呼び出されると、その実装はビジターの動的型と要素の静的型の両方に基づいて選択されます。これは、メソッドの実装内からわかるようにaccept
、要素の動的型と同じです。(ボーナスとして、ビジターが指定された要素の型の引数を処理できない場合、コンパイラはエラーをキャッチします。)
したがって、メソッドの実装は、visit
要素の動的タイプとビジターの動的タイプの両方に基づいて選択されます。これにより、二重ディスパッチが効果的に実装されます。Common LispやC#などの動的言語ランタイム(DLR)を介して、単一ディスパッチだけでなく多重ディスパッチをサポートするオブジェクト システムを持つ言語では、ビジター パターンの実装は、単純な関数オーバーロードを使用してビジットされるすべてのケースをカバーできるようにすることで大幅に簡素化されます (動的ビジターとも呼ばれます)。動的ビジターは、パブリック データのみを操作する場合、オープン/クローズ原則(既存の構造を変更しないため) と単一責任原則(ビジター パターンを別のコンポーネントに実装するため) に準拠します。
このように、要素のグラフをトラバースする 1 つのアルゴリズムを記述することができ、要素とビジターの両方の動的なタイプに基づいて要素と対話するさまざまな種類のビジターを提供することで、そのトラバース中にさまざまな種類の操作を実行できます。
C#の例
この例では、ExpressionPrintingVisitor
印刷を処理する別のクラスを宣言します。新しい具体的なビジターの導入が必要な場合は、Visitor インターフェイスを実装する新しいクラスが作成され、Visit メソッドの新しい実装が提供されます。既存のクラス (Literal および Addition) は変更されません。
システムの使用;
名前空間Wikipedia ;
パブリックインターフェースVisitor { void Visit ( Literal literal ); void Visit ( Addition addition ); }
パブリッククラスExpressionPrintingVisitor : Visitor { public void Visit ( Literal literal ) { Console.WriteLine ( literal.Value ) ; }
public void Visit ( Addition addition ) { double leftValue = addition.Left.GetValue ( ) ; double rightValue = addition.Right.GetValue ( ) ; var sum = addition.GetValue ( ) ; Console.WriteLine ( " {0} + {1} = {2} " 、leftValue 、rightValue 、sum ) ; } }
パブリック抽象クラスExpression {パブリック抽象void Accept ( Visitor v );パブリック抽象double GetValue (); }
パブリッククラスLiteral : Expression { public Literal ( double value ) { this . Value = value ; }
パブリックdouble値{取得;設定; }
パブリックオーバーライドvoid Accept ( Visitor v ) { v.Visit ( this ) ; }パブリックオーバーライドdouble GetValue ( ) { return Value ; } }
パブリッククラスAddition : Expression {パブリックAddition ( Expression left 、Expression right ) { Left = left ; Right = right ; }
パブリック式左{ get ; set ; }パブリック式右{ get ; set ; }
パブリックオーバーライドvoid Accept ( Visitor v ) { Left.Accept ( v ) ; Right.Accept ( v ) ; v.Visit ( this ) ; }パブリックオーバーライドdouble GetValue ( ) { return Left.GetValue ( ) + Right.GetValue ( ) ; } }
public static class Program { public static void Main ( string [] args ) { // 1 + 2 + 3 をエミュレートしますvar e = new Addition ( new Addition ( new Literal ( 1 ), new Literal ( 2 ) ), new Literal ( 3 ) );
var printingVisitor = new ExpressionPrintingVisitor ( ); e.Accept ( printingVisitor ) ; Console.ReadKey ( ) ; } }
Smalltalkの例
この場合、ストリーム上に自身を印刷する方法を知るのはオブジェクトの責任です。この場合の訪問者はストリームではなくオブジェクトです。
「クラスを作成するための構文はありません。クラスは他のクラスにメッセージを送信することによって作成されます。」
WriteStream サブクラス: #ExpressionPrinter
インスタンス変数名: ''
クラス変数名: ''
パッケージ: 'Wikipedia' 。
ExpressionPrinter >>write: anObject
"アクションをオブジェクトに委任します。オブジェクトは特別な
クラスである必要はなく、メッセージ #putOn: を理解できれば十分です。"
anObject putOn: self .
^ anObject .
オブジェクト サブクラス: #Expression
instanceVariableNames: ''
classVariableNames: ''
パッケージ: 'Wikipedia' 。
式 サブクラス: #Literal
インスタンス変数名: 'value'
クラス変数名: ''
パッケージ: 'Wikipedia' 。
Literal クラス>>with: aValue
"Literal クラスのインスタンスを構築するためのクラス メソッド"
^ self new
value: aValue ;
yourself 。
リテラル>>value: aValue
"値のセッター"
value := aValue 。
リテラル>>putOn: aStream
"リテラル オブジェクトは、自分自身を印刷する方法を知っています"
aStream nextPutAll: value asString 。
式 サブクラス: #Addition
インスタンス変数名: 'left right'
クラス変数名: ''
パッケージ: 'Wikipedia' 。
Addition クラス>>left: a right: b
「Addition クラスのインスタンスを構築するためのクラス メソッド」
^ self new
left: a ;
right: b ;
yourself 。
追加>>left: anExpression
"left の Setter"
left := anExpression 。
追加>>right: anExpression
"right の Setter"
right := anExpression 。
Addition >>putOn: aStream
「Addition オブジェクトは、自分自身を印刷する方法を知っています」
aStream nextPut: $( .
left putOn: aStream .
aStream nextPut: $+ .
right putOn: aStream .
aStream nextPut: $) .
オブジェクト サブクラス: #Program
インスタンス変数名: ''
クラス変数名: ''
パッケージ: 'Wikipedia' 。
プログラム>> main
| 式 ストリーム |
式 := 加算
左: (加算
左: (リテラル : 1 )
右: (リテラル : 2 ))
右: (リテラル : 3 ) 。
ストリーム := ExpressionPrinter on: (文字列 新規: 100 ) 。
ストリーム 書き込み: 式。
トランスクリプト 表示: ストリームの 内容。
トランスクリプトの フラッシュ。
行く
Goはメソッドのオーバーロードをサポートしていないため、ビジターメソッドには異なる名前が必要です。典型的なビジターインターフェースは次のようになります。
タイプVisitorインターフェース{ visitWheel ( wheel Wheel )文字列visitEngine ( engine Engine )文字列visitBody ( body Body )文字列visitCar ( car Car )文字列}
Javaの例
次の例はJava言語で記述されており、ノード ツリーの内容 (この場合は自動車のコンポーネントを記述) を印刷する方法を示しています。print
各ノード サブクラス ( Wheel
、Engine
、Body
、およびCar
) のメソッドを作成する代わりに、1 つのビジター クラス ( CarElementPrintVisitor
) が必要な印刷アクションを実行します。異なるノード サブクラスでは適切に印刷するために若干異なるアクションが必要なので、 はメソッドCarElementPrintVisitor
に渡された引数のクラスに基づいてアクションをディスパッチしますvisit
。CarElementDoVisitor
異なるファイル形式の保存操作に類似した も同様の処理を行います。
図
出典
java.util.Listをインポートします。
インターフェイス CarElement { void accept ( CarElementVisitor visitor ); }
インターフェイス CarElementVisitor { void visit ( Body body ); void visit ( Car car ); void visit ( Engine engine ); void visit ( Wheel wheel ); }
クラス Wheel はCarElementを実装します{ private final String name ;
パブリックWheel ( final String name ) { this . name = name ; }
パブリック文字列getName () {戻り値 name ; }
@Override
public void accept ( CarElementVisitor visitor ) { /* * Wheel の accept(CarElementVisitor) は CarElement の accept(CarElementVisitor) を実装しているため、accept の呼び出し は実行時にバインドされます。これは 、最初のディスパッチと見なすことができます。ただし、コンパイル時に 'this' が Wheel であることがわかっているため、 visit(Wheel) (visit(Engine) などではなく)を呼び出す決定は コンパイル時に行うことが できます。さらに、CarElementVisitor の各実装は visit(Wheel) を実装しており、これは実行時に行われる別の決定です。これは 、2 番目のディスパッチと見なすこと ができます。 */ visitor . visit ( this ); } }
クラス Body はCarElementを実装します{ @Override public void accept ( CarElementVisitor visitor ) { visitor . visit ( this ); } }
クラス Engine はCarElementを実装します{ @Override public void accept ( CarElementVisitor visitor ) { visitor . visit ( this ); } }
CarクラスはCarElementを実装します{ private final List < CarElement > elements ;
public Car () { this . elements = List . of ( new Wheel ( "front left" ), new Wheel ( "front right" ), new Wheel ( "back left" ), new Wheel ( "back right" ), new Body (), new Engine () ); }
@Override
public void accept ( CarElementVisitor visitor ) { for ( CarElement element : elements ) { element . accept ( visitor ); } visitor . visit ( this ); } }
クラス CarElementDoVisitor はCarElementVisitorを実装します{ @Override public void visit ( Body body ) { System . out . println ( "Moving my body" ); }
@Override
public void visit ( Car car ) { System . out . println ( "車を始動します" ); }
@Override
public void visit ( Wheel wheel ) { System . out . println ( "Kicking my " + wheel . getName () + " wheel" ); }
@Override
public void visit ( Engine engine ) { System . out . println ( "エンジンを起動しています" ); } }
CarElementPrintVisitorクラスはCarElementVisitorを実装します{ @Override public void visit ( Body body ) { System . out . println ( "Visiting body" ); }
@Override
public void visit ( Car car ) { System . out . println ( "Visiting car" ); }
@Override
public void visit ( Engine engine ) { System . out . println ( "Visiting engine" ); }
@Override
public void visit ( Wheel wheel ) { System . out . println ( "Visiting " + wheel . getName () + " wheel" ); } }
パブリッククラスVisitorDemo { public static void main ( final String [] args ) { Car car = new Car ();
car.accept (新しいCarElementPrintVisitor ( ) ); car.accept(新しいCarElementDoVisitor ( ) ) ; } }
出力
左前輪を訪問 右前輪を訪問 左後輪を訪問 右後輪を訪問 訪問団体 訪問エンジン 訪問車 左前輪を蹴る 右前輪を蹴る 左後輪を蹴る 右後輪を蹴る 体を動かす エンジンをかける 車を始動する
Common Lispの例
出典
( defclass auto () ((要素:initarg :要素)))
( defclass auto-part () (( name :initarg :name :initform "<名前のない自動車部品>" )))
( defmethod print-object (( p auto-part ) stream ) ( print-object ( slot-value p 'name ) stream ))
( defclassホイール(自動車部品) ())
( defclass body ( auto-part ) ())
( defclassエンジン(自動パーツ) ())
( defgeneric traverse (関数オブジェクト他のオブジェクト))
( defmethod traverse ( function ( a auto ) other-object ) ( with-slots ( elements ) a ( dolist ( e elements ) ( funcall function e other-object ))))
;; 何かをする訪問
;; すべてをキャッチ
( defmethod do-something ( object other-object ) ( format t "~s と ~s がどのように相互作用するべきかわかりません~%" object other-object ))
;; ホイールと整数を含む訪問
( defmethod do-something (( object wheel ) ( other-object integer )) ( format t "kicking wheel ~s ~s times~%" object other-object ))
;; ホイールとシンボルを含む訪問
( defmethod do-something (( object wheel ) ( other-object symbol )) ( format t "kicking wheel ~s symbolically using symbol ~s~%" object other-object ))
( defmethod do-something ((オブジェクトengine ) (他のオブジェクトinteger )) (フォーマットt "エンジンを ~s ~s 回~% 起動しています"オブジェクト他のオブジェクト))
( defmethod do-something ((オブジェクトエンジン) (その他のオブジェクトシンボル)) (フォーマットt "シンボル ~s~% を使用してエンジン ~s をシンボリックに起動しています"オブジェクトその他のオブジェクト))
( let (( a ( make-instance 'auto :elements ` ( , ( make-instance 'wheel :name "front-left-wheel" ) , ( make-instance 'wheel :name "front-right-wheel" ) , ( make-instance 'wheel :name "rear-left-wheel" ) , ( make-instance 'wheel :name "rear-right-wheel" ) , ( make-instance 'body :name "body" ) , ( make-instance 'engine :name "engine" ))))) ;; traverse して elements を出力します;; stream *standard-output* はここでは other-object の役割を果たします( traverse #' print a *standard-output* )
( terpri ) ;; 改行を印刷
;; 他のオブジェクトから任意のコンテキストでトラバースする
( traverse #' do-something a 42 )
;; 他のオブジェクトから任意のコンテキストでトラバースする
( traverse #' do-something a 'abc ))
出力
「左前輪」 「右前輪」 「後左輪」 「右後輪」 "体" "エンジン" 左前輪を蹴る 42回 蹴る車輪「前右車輪」42回 左後輪を蹴る 42回 キックホイール「後右輪」42回 「body」と42がどのように相互作用するのか分からない エンジン始動「エンジン」42回 記号ABCを使用して象徴的に「前左車輪」を蹴る車輪 記号ABCを使用して象徴的に車輪「前右車輪」を蹴る 記号ABCを使用して象徴的に「後輪左」を蹴る車輪 記号ABCを使用して象徴的に「後右車輪」を蹴る車輪 「body」とABCがどのように相互作用するかわからない シンボルABCを使用してエンジン「エンジン」を象徴的に始動する
注記
ではパラメータother-object
は不要ですtraverse
。その理由は、レキシカルにキャプチャされたオブジェクトを使用して目的のターゲット メソッドを呼び出す匿名関数を使用できるためです。
( defmethod traverse ( function ( a auto )) ;; other-object removed ( with-slots ( elements ) a ( dolist ( e elements ) ( funcall function e )))) ;; ここからも
;; ...
;; print-traverse ( traverse ( lambda ( o ) ( print o *標準出力* )) a )の別の方法
;; a と整数 42 の要素を使って何かを行う別の方法( traverse ( lambda ( o ) ( do-something o 42 )) a )
ここで、多重ディスパッチは匿名関数の本体から発行された呼び出しで発生するため、これはtraverse
単にオブジェクトの要素に関数の適用を分散するマッピング関数です。したがって、マッピング関数を除いて、ビジター パターンの痕跡はすべて消えます。マッピング関数には、2 つのオブジェクトが関係しているという証拠はありません。2 つのオブジェクトがあることと、それらの型のディスパッチに関するすべての情報は、ラムダ関数にあります。
Pythonの例
Python は、従来の意味でのメソッドのオーバーロード (渡されたパラメータのタイプに応じたポリモーフィックな動作) をサポートしていないため、異なるモデル タイプの "visit" メソッドには異なる名前を付ける必要があります。
出典
「
訪問者パターンの例
」
abc からABCMetaをインポートし 、抽象メソッド
NOT_IMPLEMENTED = "これを実装する必要があります。"
クラス CarElement (メタクラス= ABCMeta ):
@abstractmethod
def accept ( self , visitor ):
NotImplementedError ( NOT_IMPLEMENTED )を発生させます
クラス Body ( CarElement ):
def accept ( self , visitor ):
visitor . visitBody ( self )
クラス Engine ( CarElement ):
def accept ( self , visitor ):
visitor . visitEngine ( self )
クラス Wheel ( CarElement ):
def __init__ ( self , name ):
self . name = name
def accept ( self , visitor ):
visitor . visitWheel ( self )
class Car ( CarElement ):
def __init__ ( self ):
self . elements = [
Wheel ( "前左" ), Wheel ( "前右" ),
Wheel ( "後左" ), Wheel ( "後右" ),
Body ( ), Engine ( )
]
def accept ( self , visitor ):
for element in self . elements :
element . accept ( visitor )
visitor . visitCar ( self )
クラス CarElementVisitor ( metaclass = ABCMeta ):
@abstractmethod
def visitBody ( self 、 element )
: raise NotImplementedError ( NOT_IMPLEMENTED )
@abstractmethod
def visitEngine ( self 、 element ): raise NotImplementedError ( NOT_IMPLEMENTED ) @abstractmethod def visitWheel ( self 、element ): raise NotImplementedError ( NOT_IMPLEMENTED ) @abstractmethod def visitCar ( self 、element ): raise NotImplementedError ( NOT_IMPLEMENTED )
class CarElementDoVisitor ( CarElementVisitor ):
def visitBody ( self , body ):
print ( "体を動かしています。" )
def visitCar ( self , car ):
print ( "車を始動しています。" )
def visitWheel ( self , wheel ):
print ( "ホイールを蹴っています。" . format ( wheel . name ))
def visitEngine ( self , engine ):
print ( "エンジンを始動しています。" )
クラス CarElementPrintVisitor ( CarElementVisitor ):
def visitBody ( self , body ):
print ( "body を訪問しています。" )
def visitCar ( self , car ):
print ( "car を訪問しています。" )
def visitWheel ( self , wheel ):
print ( " {} wheel を訪問しています。" . format ( wheel . name ))
def visitEngine ( self , engine ):
print ( "engine を訪問しています。" )
car = Car ()
car.accept ( CarElementPrintVisitor ( ) ) car.accept ( CarElementDoVisitor ( ) )
出力
左前輪を訪ねる。
右前輪を訪ねる
。
左後輪を訪ねる。右後輪を訪ねる。
身体を訪ねる。
エンジンを訪ねる。
車を訪ねる。
左前輪を蹴る。
右前輪を蹴る。
左後輪を蹴る。
右後輪を蹴る。
身体を動かす。
エンジンをかける。
車を始動する。
抽象化
Python 3 以降を使用すると、accept メソッドの一般的な実装が可能になります。
クラス Visitable :
def accept ( self , visitor ):
lookup = "visit_" + self . __qualname__ . replace ( "." , "_" )
return getattr ( visitor , lookup )( self )
すでに実装されているクラスにフォールバックしたい場合は、これを拡張してクラスのメソッド解決順序を反復処理することができます。また、サブクラス フック機能を使用して、事前にルックアップを定義することもできます。
関連するデザインパターン
- イテレータパターン– トラバースされたオブジェクト内で型の区別をせずに、ビジターパターンのようなトラバース原則を定義します。
- チャーチ エンコーディング– 関数型プログラミングの関連概念で、タグ付きユニオン/合計型を、そのような型の「ビジター」の動作を使用してモデル化することができ、ビジター パターンでバリアントとパターンをエミュレートできるようになります。
参照
参考文献
- ^ ab Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides (1994)。デザインパターン: 再利用可能なオブジェクト指向ソフトウェアの要素。Addison Wesley。pp. 331ff。ISBN 0-201-63361-2。
{{cite book}}
: CS1 maint: 複数の名前: 著者リスト (リンク) - ^ 訪問者パターンの実例
- ^ abcd Budd, Timothy (1997). オブジェクト指向プログラミング入門(第2版). マサチューセッツ州レディング: Addison-Wesley. ISBN 0-201-82419-1. OCLC 34788238.
- ^ 「Visitor デザイン パターン - 構造とコラボレーション」w3sDesign.com 。 2017 年 8 月 12 日閲覧。
- ^ Reddy, Martin (2011). C++ の API設計。ボストン: Morgan Kaufmann。ISBN 978-0-12-385004-1. OCLC 704559821.
外部リンク
- Wayback Machineの Visitor ファミリーのデザイン パターン(2015 年 10 月 22 日アーカイブ)。追加のアーカイブ: 2004 年 4 月 12 日、2002 年 3 月 5 日。Robert C. Martin著のThe Principles, Patterns, and Practices of Agile Software Developmentの抜粋 ( Prentice Hall)
- UML と LePUS3 (設計記述言語) のビジター パターン
- 記事「コンポーネント化: ビジターの例」、Bertrand Meyerおよび Karine Arnout 著、Computer (IEEE)、第 39 巻、第 7 号、2006 年 7 月、23 ~ 30 ページ。
- 記事 訪問者パターンの型理論的再構築
- Jens Palsberg と C. Barry Jay による記事「Visitor パターンの本質」。1997 年のIEEE-CS COMPSAC論文では、リフレクションが使用できる場合は accept() メソッドは不要であることが示され、この手法に対して「Walkabout」という用語が導入されています。
- ブルース・ウォレスによる記事「リフレクションの時間」 – サブタイトルは「Java 1.2 のリフレクション機能により、Visitor パターンから面倒な accept() メソッドが排除される」
- リフレクション(Java)を使用したビジターパターン。
- PerfectJPattern オープン ソース プロジェクトは、デリゲートに基づいて、Java でビジター パターンのコンテキストフリーかつ型セーフな実装を提供します。
- 訪問者デザインパターン