どうも、ちゃんなるです!
今回は、Visitor Patternを紹介します🖐️
概要
Visitor Patternは、オブジェクトのデータ構造とその操作を分離するデザインパターンです。
ざっくり下記のようにまとめられます。
Visitor
:訪問者
データ構造
と処理
を分離するデータ構造
の中を巡り歩く主体である訪問者(Visitor)
を表すクラスを用意し、そのクラスに処理
を任せる- 新しい
処理
を追加したいときは新しい訪問者(Visitor)
を作れば良い データ構造
の方は戸を叩いてくる訪問者(Visitor)
を受け入れ(accept
)てあげれば良い
- 新しい
これにより、新しい操作を追加する際に、既存のクラスを変更せずに済むため、オブジェクトのデータ構造が安定している場合に特に有用です。
Visitor Patternの登場人物
役割 | 説明 |
---|---|
Visitor |
ConcreteElement ごとにvisit メソッドを定義する抽象クラス |
ConcreteVisitor |
visit メソッドを実装する具体クラス |
Element |
Visit の訪問先で、受入用のaccept メソッドを定義する抽象クラス |
ConcreteElement |
Element のインタフェースを実装する具体クラス |
ObjectStructure |
Element の集合を扱う。個々のElement を扱えるようIterator を実装することも。 |
今回示すプログラムの設計
「動物園で動物のエサの総重量を計算する」というシナリオを想定します🐈🐕🦁
サンプルコード
// Visitor interface AnimalVisitor { visitMammal(mammal: Mammal): void; visitBird(bird: Bird): void; } // ConcreteVisitor class FeedVisitor implements AnimalVisitor { totalFeedWeight = 0; visitMammal(mammal: Mammal): void { this.totalFeedWeight += mammal.feedWeight; } visitBird(bird: Bird): void { this.totalFeedWeight += bird.feedWeight; } } // ConcreteVisitor class WeightVisitor implements AnimalVisitor { totalWeight = 0; visitMammal(mammal: Mammal): void { this.totalWeight += mammal.weight; } visitBird(bird: Bird): void { this.totalWeight += bird.weight; } }
// Element abstract class Animal { abstract accept(visitor: AnimalVisitor): void; } // ConcreteElement class Mammal extends Animal { constructor(public feedWeight: number, public weight: number) { super(); } accept(visitor: AnimalVisitor): void { visitor.visitMammal(this); } } // ConcreteElement class Bird extends Animal { constructor(public feedWeight: number, public weight: number) { super(); } accept(visitor: AnimalVisitor): void { visitor.visitBird(this); } }
では、Elementを保持するデータ構造を定義します。
// ObjectStructure class Zoo { private animals: Animal[] = []; addAnimal(animal: Animal): void { this.animals.push(animal); } calculate(visitor: AnimalVisitor): number { this.animals.forEach(animal => animal.accept(visitor)); if (visitor instanceof FeedVisitor) { return visitor.totalFeedWeight; } else if (visitor instanceof WeightVisitor) { return visitor.totalWeight; } return 0; } }
では、実際に使ってみましょう。
const zoo = new Zoo(); zoo.addAnimal(new Mammal(10, 50)); // カンガルー zoo.addAnimal(new Mammal(20, 200)); // クマ zoo.addAnimal(new Bird(5, 15)); // インコ zoo.addAnimal(new Bird(3, 7)); // スズメ const feedVisitor = new FeedVisitor(); const weightVisitor = new WeightVisitor(); const totalFeedWeight = zoo.calculate(feedVisitor); const totalWeight = zoo.calculate(weightVisitor); console.log(`Total feed weight: ${totalFeedWeight}kg`); // Total feed weight: 38kg console.log(`Total animal weight: ${totalWeight}kg`); // Total animal weight: 272kg
クラス図
Visitor Patternの使い道
- オブジェクトの構造が安定しており、その構造に対する操作を柔軟に追加・変更したい場合
- 構造内の各要素に対して、状態に依存しない操作を行いたい場合
組み合わせられるデザインパターン
Composite Pattern
: オブジェクトの階層構造を表現するために使用できます。
Iterator Pattern
: 要素の集合体を順番に操作する際に適用できます。
まとめ
Visitor Patternは、オブジェクトの構造と操作を分離するデザインパターンです。
新しい操作を追加する際に既存のクラスを変更せずに済むため、特にオブジェクトの構造が安定している場合に有用です。
是非、あなたのプロジェクトにVisitor Patternを活用してみてください👍