【TypeScript】実践的な設計パターン - 実装例集

【TypeScript】実践的な設計パターン - 実装例集

2024-11-10

2024-11-10

概要

本記事では、TypeScriptを使った代表的な設計パターンの実装例を紹介します。設計パターンは、再利用性の高いコードを効率的に設計するためのテンプレートとして機能し、開発効率や保守性を向上させます。TypeScriptで利用できる設計パターンを通して、柔軟で堅牢なコードの書き方を学んでいきましょう。

設計パターンの基本とTypeScriptの特性

設計パターンは、オブジェクト指向プログラミングにおける問題を解決するためのテンプレートです。TypeScriptは、JavaScriptに型の安全性とクラスベースの構造を追加するため、設計パターンの実装に適しています。以下では、FactorySingletonStrategyObserverの各パターンを取り上げます。

Factoryパターン - オブジェクト生成の統一

Factoryパターンは、オブジェクト生成を専門のクラスに任せることで、クライアントコードから具体的なクラスの生成方法を隠蔽するデザインパターンです。このパターンにより、コードが柔軟で拡張可能になります。

// インターフェース定義
interface Animal {
  makeSound(): void;
}
// 具体的なクラス
class Dog implements Animal {
  makeSound(): void {
    console.log("Woof!");
  }
}
class Cat implements Animal {
  makeSound(): void {
    console.log("Meow!");
  }
}
// Factoryクラス
class AnimalFactory {
  static createAnimal(type: string): Animal {
    switch (type) {
      case "dog":
        return new Dog();
      case "cat":
        return new Cat();
      default:
        throw new Error("Unknown animal type");
    }
  }
}
// 使用例
const animal = AnimalFactory.createAnimal("dog");
animal.makeSound(); // Woof!

この例では、AnimalFactoryが動物の種類に応じてDogまたはCatのインスタンスを生成します。Factoryパターンを使用することで、クライアントコードは動物の生成方法に依存せずに利用できます。

Singletonパターン - インスタンスの一意性を保証

Singletonパターンは、あるクラスに対して唯一のインスタンスしか存在しないことを保証し、グローバルなアクセスポイントを提供するデザインパターンです。設定管理やログ機能のようなユースケースで役立ちます。

class Database {
  private static instance: Database;
  private constructor() {
    // プライベートコンストラクタにより直接インスタンス化を防止
  }
  static getInstance(): Database {
    if (!Database.instance) {
      Database.instance = new Database();
    }
    return Database.instance;
  }
  connect(): void {
    console.log("Database connected.");
  }
}
// 使用例
const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // true
db1.connect(); // Database connected.

getInstanceメソッドで初めてインスタンスを生成し、それ以降は同じインスタンスを返すため、インスタンスが一意であることを保証します。

Strategyパターン - アルゴリズムの切り替え

Strategyパターンは、特定の機能を複数の異なるアルゴリズムで実行できるようにするためのデザインパターンです。具体的なアルゴリズムをクラスとして実装し、実行時に切り替えられるため、動的な挙動の変更が可能になります。

// Strategyインターフェース
interface PaymentStrategy {
  pay(amount: number): void;
}
// 具体的なStrategyクラス
class CreditCardPayment implements PaymentStrategy {
  pay(amount: number): void {
    console.log(`Paid ${amount} using Credit Card`);
  }
}
class PayPalPayment implements PaymentStrategy {
  pay(amount: number): void {
    console.log(`Paid ${amount} using PayPal`);
  }
}
// Contextクラス
class ShoppingCart {
  private paymentStrategy: PaymentStrategy;
  constructor(paymentStrategy: PaymentStrategy) {
    this.paymentStrategy = paymentStrategy;
  }
  checkout(amount: number): void {
    this.paymentStrategy.pay(amount);
  }
}
// 使用例
const cart = new ShoppingCart(new CreditCardPayment());
cart.checkout(100); // Paid 100 using Credit Card
const cart2 = new ShoppingCart(new PayPalPayment());
cart2.checkout(150); // Paid 150 using PayPal

ShoppingCartクラスに支払い方法を動的に設定できるため、柔軟な拡張が可能です。

Observerパターン - イベント通知の実装

Observerパターンは、一つのオブジェクトの状態が変化したときに、依存する他のオブジェクトに自動的に通知するデザインパターンです。イベントの購読やリアルタイム通知の実装に役立ちます。

// Subjectインターフェース
interface Subject {
  registerObserver(observer: Observer): void;
  removeObserver(observer: Observer): void;
  notifyObservers(): void;
}
// Observerインターフェース
interface Observer {
  update(state: string): void;
}
// 具体的なSubjectクラス
class NewsAgency implements Subject {
  private observers: Observer[] = [];
  private state: string = "";
  registerObserver(observer: Observer): void {
    this.observers.push(observer);
  }
  removeObserver(observer: Observer): void {
    this.observers = this.observers.filter(obs => obs !== observer);
  }
  notifyObservers(): void {
    this.observers.forEach(observer => observer.update(this.state));
  }
  setState(state: string): void {
    this.state = state;
    this.notifyObservers();
  }
}
// 具体的なObserverクラス
class NewsReader implements Observer {
  update(state: string): void {
    console.log(`News update: ${state}`);
  }
}
// 使用例
const agency = new NewsAgency();
const reader1 = new NewsReader();
const reader2 = new NewsReader();
agency.registerObserver(reader1);
agency.registerObserver(reader2);
agency.setState("Breaking news!"); // Both readers receive the news update

NewsAgencyが通知対象のObserverに状態を更新するたびに通知する構造で、リアルタイムなイベント処理が可能です。

設計パターンの選択と実装時のポイント

  1. Factoryパターンは、インスタンス生成が複雑な場合や、将来的に生成するクラスを増やす可能性がある場合に適しています。
  2. Singletonパターンは、設定情報やログ記録など、システム全体で一つのインスタンスのみ必要な場合に役立ちます。
  3. Strategyパターンは、同じ処理を異なる方法で実行できる柔軟性 が求められる場面で有効です。
  4. Observerパターンは、リアルタイムで通知を行いたい場合や、イベントドリブンな設計に適しています。

まとめ

TypeScriptを用いた設計パターンの実装は、コードの再利用性や保守性を大きく向上させます。Factory、Singleton、Strategy、Observerといった代表的な設計パターンは、システムの規模や複雑性にかかわらず役立ち、設計をシンプルかつ強固にします。各パターンの特徴と実装方法を理解し、システムに適したパターンを取り入れることで、より効果的な開発を実現しましょう。

Recommend