前のエントリーでStrategyパターンで作った引力モデルのシミュレーションとほぼ同じものを、今度はVisitorパターンで作ってみる。
Strategyパターンの場合との全体的な構造上の違いとして、本体のクラスに切替可能な処理(またはアルゴリズム)への関連が無く、処理を実装するクラスから本体のクラス階層への関連がある。具体的には、PhysicalObjectクラスにVisitorのメンバが無く、Visitorの具象クラスから本体の具象クラスへの依存がある。
切替可能な処理の構造の違いとしては、Strategyパターンでは本体の具象クラス毎の既定の処理をStrategyでなく本体側に入れることが可能であったのに対して、VisitorパターンではそれらをVisitor側に定義する必要がある。もし本体側に実装しても、それを呼び出す仕組みが必要になるので、Visitor側にクラスを定義するのは避けられない。
また、Strategyパターンでは処理が無い場合は無処理のStrategyを定義する必要があった(特にStrategyが値を返す場合)が、Visitorパターンでは無処理ならVisitorを定義する必要が無い。
具体的には、各PhysicalObjectの標準の処理の実装のために、DefaultGravity, DefaultColorizerというクラスを定義しており、無重力の場合は引力計算の処理の必要が無いので、NoGravityApplierはのようなクラスを定義していない。
なお、ColorVisitorのサブクラスにて各PhysicalObjectの標準色を使う場合があるため、
標準色の設定処理はColorVisitorクラスに実装するようにし、DefaultColorizerは、ColorVisitorをそのまま引き継ぐ、インスタンス生成可能にするためだけの空のクラスとしている。ColorVisitorを抽象クラスでなく具象クラスにすればDefaultColorizerクラスが不要になるように思えるが、そうすると、ColorVisitorが標準色の設定の役割を兼ね、ColorVisitorのサブクラスが標準色の設定の役割を持つColorVisitorとして振る舞えることになってしまい、いわゆるリスコフの置換原則に反してしまうので、好ましくない。
以上の方針で実装したのが、下のリンク先のページのJavaアプレットである。(ソースコードへのリンクもあり)
・VisitorSample2のアプレットとソースコードのページ
StrategyパターンのサンプルではボタンによるStrategyの切替がそれ以後に追加される物体のみに有効になるのに対し、Visitorパターンのサンプルでは、フィールド上にある全ての物体に対して切り替わる。
WiredStarについては、同じVisitorでもvisit先の具象クラスによって処理を変更する例として、全てのGravityVisitorのvisit()を空にし、常に無重力の状態にしている。
オブジェクト毎に別々のVisitorを適用するようにしていないのは、そうするためにはアプリケーション側でオブジェクトとVisitorとの対応を管理しないといけなくなり、Visitorパターンのメリットが大きく損なわれるからである。
次のエントリーで、StrategyパターンとVisitorパターンの例に対して、本体と切替可能処理のそれぞれのクラス階層にクラスを追加してみる。(続く)
本体のクラス階層から見て、GravityVisitorとColorVisitorとの区別を無くしているのは、区別する意味が無いと考えたからである。
もし、accept()がvisit()する時にVisitorの種類によって異なるパラメーターを渡すようにするとか、Visitorの種類によってはaccept()が任意のパラメーターを受けてそのままvisit()に渡すようにするとかの場合ならVisitorを区別する意味はあると思うが、Visitorの種類によってaccept()を分けるのは、Visitorの抽象化が中途半端で、Visitorパターンの特徴を傷付けている感じがする。Visitorパターンの特徴が、Visitorでさえあればaccept()することだと考えると、Visitorとしての特徴付けは、どのクラス階層へのVisitorであるかだけであるべきであり、少なくともどのような類の処理を行うVisitorであるかではないべきだと思う。
任意のパラメーターをaccept()に渡してvisit()で受け取るVisitorとそうでないVisitorの2種類を定義するのなら、Visitorパターンとして間違ってはいないと思うが、一般にVisitorバターンではVisitorの方がフレキシブルであり、もし任意のパラメーターを渡す必要があるVisitorが後から発生すれば、その時に全てのVisitorがそれに合わせれば良いので、そういうことが必要なケースは極めて稀だと考える。
なお、前項で書き忘れたが、ソースコードのアプリケーションクラスをappとfieldに分けているのは、なるべくGUI(画面仕様)依存の部分を切り分けたかったためで、appをGUI依存の部分、fieldを非依存の部分と分けたつもりのものである。
コメント