ちゃんなるぶろぐ

エンジニア5年生🧑‍💻 オライリーとにらめっこする毎日。

【5分でわかる👤】TypeScriptでDesign Pattern〜Visitor Pattern〜

どうも、ちゃんなるです!

今回は、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

クラス図

サンプルコードのクラス図:Mermaid Live Editorで作成

Visitor Patternの使い道

  • オブジェクトの構造が安定しており、その構造に対する操作を柔軟に追加・変更したい場合
  • 構造内の各要素に対して、状態に依存しない操作を行いたい場合

組み合わせられるデザインパターン

  • Composite Pattern: オブジェクトの階層構造を表現するために使用できます。

chan-naru.hatenablog.com

  • Iterator Pattern: 要素の集合体を順番に操作する際に適用できます。

chan-naru.hatenablog.com

まとめ

Visitor Patternは、オブジェクトの構造と操作を分離するデザインパターンです。

新しい操作を追加する際に既存のクラスを変更せずに済むため、特にオブジェクトの構造が安定している場合に有用です。

是非、あなたのプロジェクトにVisitor Patternを活用してみてください👍

参考文献

www.oreilly.com

en.wikipedia.org