Visitor は、 振る舞いに関するデザインパターンの一つで、 既存コードを変更することなく、 既存のクラス階層に新しい振る舞いの追加を可能とします。
Visitor の代わりに単純にメソッドの多重定義 (overload) を使うことができない理由については、 別の記事 『ビジターと二重ディスパッチ』 を参照。
概念的な例
Visitor パターンを使うと、 ある構造体への新しい振る舞いの追加を、 構造体そのものを変更することなく行えます。 以下のような図形を表現する構造体を含むライブラリーの保守をしているとしましょう:
- Square (正方形)
- Circle (円)
- Triangle (三角形)
上記の図形構造体は、 それぞれ共通の図形インターフェースを実装します。
会社の人たちが、 あなたの素晴らしいライブラリーを使い始めるとすぐに、 新機能リクエストが殺到しました。 一番簡単なのを見てみましょう: あるチームからは、 shape 構造体に getArea
(面積を取得) 機能を追加して欲しいというリクエストが来ました。
この問題を解決するには、 いくつかの選択肢があります。
最初に思い浮かぶ選択肢は、 shape インターフェースに直接 getArea
メソッドを追加し、 shape を実装した構造体それぞれで実装することです。 これは、 まったくもっともで、 すぐに実施すべきなように思えますが、 タダというわけではありません。 ライブラリーの保守担当者としては、 誰かが新機能をリクエストするたびに、 大切なコードを壊してしまうリスクを負いたくありません。 それでも、 他のチームにライブラリーの拡張ができるようにしてあげたいと思っています。
二つ目の選択肢は、 新機能をリクエストしているチームが自分たちでそれを実装できるようにすることです。 しかし、 新規の振る舞いは非公開のコードに依存しているかもしれないので、 これが常に可能とは限りません。
三つ目の選択肢は、 Visitor パターンを使ってこの問題を解くことです。 まず、 このようなビジター・インターフェースを定義します:
visitForSquare(square)
、 visitForCircle(circle)
、 visitForTriangle(triangle)
といった関数は、 それぞれ、 正方形、 円、 三角形に機能を追加します。
何でビジター・インターフェースに visit(shape)
というメソッドを一つだけ追加しないんだと疑問に思いますか? 理由は、 Go 言語がメソッドの多重定義をサポートしていないからです。 パラメーターだけ異なり名前が同じメソッドは許可されていません。
二つ目の重要な事項は、 shape インターフェースに、 accept
メソッドを追加することです。
shape 構造体はすべて、 このメソッドを定義する必要があります。 例:
ちょっと待って! さっき既存の構造体は変更したくないと言わなかったですか? 残念なことに、 Visitor パターンを利用する時は、 shape の構造体を変更する必要があります。 しかし、 この変更は一度だけです。
getNumSides
とか getMiddleCoordinates
のような振る舞いを追加する場合でも、 同じ accept(v visitor)
関数が使えるので、 shape 構造体への更なる変更は不要です。
つまり、 shape 構造体は一度だけ変更する必要があり、 将来のすべての機能追加リクエストは、 同一の accept 関数で対処できます。 チームが、 getArea
機能の追加をリクエストした場合、 単にビジター・インターフェースの具象実装を定義し、 そこに面積計算のロジックを書くだけですみます。
shape.go: 要素
square.go: 具象要素
circle.go: 具象要素
rectangle.go: 具象要素
visitor.go: ビジター
areaCalculator.go: 具象ビジター
middleCoordinates.go: 具象ビジター
main.go: クライアント・コード
output.txt: 実行結果