1. パターンの意図
ビジター(Visitor)パターン は、
データ構造を変えずに新しい操作を追加できるようにする デザインパターンです。
解決する問題
- 既存のオブジェクト構造を変えずに、新しい処理を追加したい
- if/else や型判定で処理を分けるのを避けたい
- データ構造(要素)は安定しているが、操作は頻繁に追加・変更される場合
ポイント
- Element(要素):訪問対象(データ構造の一部)
- Visitor:操作を表すインターフェース
- ConcreteVisitor:新しい処理を追加するクラス
- Element は accept(visitor) を持ち、Visitor の visitXXX() を呼び出す(二重ディスパッチ)
2. UML 図
3. Flutter / Dart 実装例
Element
abstract class Element {
void accept(Visitor visitor);
}
class ConcreteElementA implements Element {
void operationA() => print("ElementA operation");
@override
void accept(Visitor visitor) => visitor.visitElementA(this);
}
class ConcreteElementB implements Element {
void operationB() => print("ElementB operation");
@override
void accept(Visitor visitor) => visitor.visitElementB(this);
}
Visitor
abstract class Visitor {
void visitElementA(ConcreteElementA element);
void visitElementB(ConcreteElementB element);
}
class PrintVisitor implements Visitor {
@override
void visitElementA(ConcreteElementA element) {
element.operationA();
print("Visited ElementA");
}
@override
void visitElementB(ConcreteElementB element) {
element.operationB();
print("Visited ElementB");
}
}
利用例
void main() {
List<Element> elements = [ConcreteElementA(), ConcreteElementB()];
var visitor = PrintVisitor();
for (var e in elements) {
e.accept(visitor);
}
}
出力:
ElementA operation
Visited ElementA
ElementB operation
Visited ElementB
4. Android / Kotlin 実装例
Element
interface Element {
fun accept(visitor: Visitor)
}
class ConcreteElementA : Element {
fun operationA() = println("ElementA operation")
override fun accept(visitor: Visitor) = visitor.visitElementA(this)
}
class ConcreteElementB : Element {
fun operationB() = println("ElementB operation")
override fun accept(visitor: Visitor) = visitor.visitElementB(this)
}
Visitor
interface Visitor {
fun visitElementA(element: ConcreteElementA)
fun visitElementB(element: ConcreteElementB)
}
class PrintVisitor : Visitor {
override fun visitElementA(element: ConcreteElementA) {
element.operationA()
println("Visited ElementA")
}
override fun visitElementB(element: ConcreteElementB) {
element.operationB()
println("Visited ElementB")
}
}
利用例
fun main() {
val elements: List<Element> = listOf(ConcreteElementA(), ConcreteElementB())
val visitor = PrintVisitor()
elements.forEach { it.accept(visitor) }
}
5. 実務ユースケース
Flutter
- AST(抽象構文木)のノードを走査して、コード生成や最適化を行う
- JSON データ構造に対して複数の処理(検証 / 出力 / 変換)を追加
- Widget ツリーやカスタム DSL の評価
Android / Kotlin
- コンパイラの AST 処理(Kotlin Compiler Plugin)
- XML / JSON の構文解析・変換処理
- ファイルシステムや UI コンポーネントツリーの走査
6. メリット / デメリット
メリット
- 新しい処理を Visitor として追加できる(既存の Element を修正せずに拡張可能)
- データ構造と処理を分離できる
- OCP(開放閉鎖原則)に従いやすい
デメリット
- Element の種類が増えると Visitor 側も修正が必要
- 二重ディスパッチで仕組みがやや複雑
- 小規模な処理ではオーバーエンジニアリング
まとめ
- Visitor パターンは「データ構造を変えずに新しい処理を追加できる」
- Flutter/Android では AST, JSON, ツリー構造の処理に有効
- Strategy = 処理切替 / State = 状態切替 / Visitor = 処理追加 と整理するとわかりやすい