【TypeScript】メディエーターパターンの型定義 - 複雑なオブジェクト間通信を管理

【TypeScript】メディエーターパターンの型定義 - 複雑なオブジェクト間通信を管理

2024-11-10

2024-11-10

TypeScriptとメディエーターパターンの型安全な実装

メディエーターパターンは、複数のオブジェクトが直接相互通信を行うのではなく、中央のメディエーター(仲介者)を通じて通信するように設計するデザインパターンです。TypeScriptでメディエーターパターンを実装する際には、インターフェースやジェネリクスを活用することで、通信を型安全に管理でき、複雑な依存関係を整理しつつ拡張性のあるコードを構築できます。

メディエーターパターンの基本構造

メディエーターパターンの主な構成要素は以下のとおりです。

  1. Mediator(メディエーター): 中央でオブジェクト間の通信を仲介するインターフェースやクラス。
  2. ConcreteMediator(具体的メディエーター): Mediatorを実装し、オブジェクト間の通信ルールを定義。
  3. Colleague(同僚オブジェクト): メディエーターを介して他のオブジェクトと通信を行うオブジェクト。メディエーターに依存し、直接的に他のオブジェクトに依存しません。 メディエーターパターンにより、各オブジェクトの役割と通信のルールをメディエーターに集約できるため、オブジェクト間の依存関係が減り、保守性が向上します。

基本的なメディエーターパターンの実装

まず、TypeScriptでのメディエーターパターンの基本的な実装を示します。ここでは、ユーザー間でメッセージを送受信するシンプルなチャットシステムを例にとります。

// Mediatorインターフェース
interface ChatMediator {
  sendMessage(message: string, sender: User): void;
  addUser(user: User): void;
}
// Colleagueクラス
class User {
  private mediator: ChatMediator;
  private name: string;
  constructor(name: string, mediator: ChatMediator) {
    this.name = name;
    this.mediator = mediator;
  }
  send(message: string): void {
    console.log(`${this.name} からのメッセージ: ${message}`);
    this.mediator.sendMessage(message, this);
  }
  receive(message: string): void {
    console.log(`${this.name} に届いたメッセージ: ${message}`);
  }
  getName(): string {
    return this.name;
  }
}
// ConcreteMediatorクラス
class ChatRoom implements ChatMediator {
  private users: User[] = [];
  addUser(user: User): void {
    this.users.push(user);
  }
  sendMessage(message: string, sender: User): void {
    this.users.forEach(user => {
      if (user !== sender) {
        user.receive(`${sender.getName()}: ${message}`);
      }
    });
  }
}

このコードでは、ChatMediatorインターフェースがメッセージ送信とユーザー登録のメソッドを提供し、ChatRoomがその具体的なメディエーターの実装として振る舞います。また、各Userはメディエーター経由で他のユーザーにメッセージを送信します。

使用例

const chatRoom = new ChatRoom();
const user1 = new User("Alice", chatRoom);
const user2 = new User("Bob", chatRoom);
const user3 = new User("Charlie", chatRoom);
chatRoom.addUser(user1);
chatRoom.addUser(user2);
chatRoom.addUser(user3);
user1.send("こんにちは、みんな!");  
user2.send("こんにちは、Alice!");

このコードを実行すると、Aliceが「こんにちは、みんな!」というメッセージを送ると、他のユーザー全員がそのメッセージを受け取ります。

型安全なメディエーターパターンの実装

TypeScriptでは、メディエーターとコリーグのやり取りに型安全性を追加できます。以下では、ジェネリクスを用いて、各オブジェクトが送信するメッセージの型を定義します。

型定義付きのメディエーターパターン実装

ここでは、通知のメッセージ内容とユーザーの識別情報を型で定義し、通知管理を行うメディエーターパターンを構築します。

// メッセージデータの型
interface Notification {
  title: string;
  content: string;
}
// Mediatorインターフェース
interface NotificationMediator {
  sendNotification(notification: Notification, sender: Notifiable): void;
  addSubscriber(subscriber: Notifiable): void;
}
// Colleagueインターフェース
interface Notifiable {
  receiveNotification(notification: Notification): void;
  getName(): string;
}
// ConcreteMediatorクラス
class NotificationCenter implements NotificationMediator {
  private subscribers: Notifiable[] = [];
  addSubscriber(subscriber: Notifiable): void {
    this.subscribers.push(subscriber);
  }
  sendNotification(notification: Notification, sender: Notifiable): void {
    this.subscribers.forEach(subscriber => {
      if (subscriber !== sender) {
        subscriber.receiveNotification(notification);
      }
    });
  }
}
// Colleagueクラス
class User implements Notifiable {
  private name: string;
  constructor(name: string) {
    this.name = name;
  }
  receiveNotification(notification: Notification): void {
    console.log(`${this.name} に通知: ${notification.title} - ${notification.content}`);
  }
  getName(): string {
    return this.name;
  }
}

Notification型を使うことで、メッセージのタイトルとコンテンツを一貫して管理し、誤ったデータの送信を防ぎます。また、Notifiableインターフェースを実装したユーザーは、NotificationCenterのメディエーターを介して通知を受け取ることができます。

使用例

const notificationCenter = new NotificationCenter();
const user1 = new User("Alice");
const user2 = new User("Bob");
const user3 = new User("Charlie");
notificationCenter.addSubscriber(user1);
notificationCenter.addSubscriber(user2);
notificationCenter.addSubscriber(user3);
const notification: Notification = {
  title: "新しいメッセージ",
  content: "最新のお知らせがあります。"
};
notificationCenter.sendNotification(notification, user1);

この例では、user1が通知を送信すると、他のユーザーにのみ通知が配信されます。

メディエーターパターンの応用例 - 複数のメッセージタイプに対応

さらに、メディエーターが複数のメッセージタイプに対応するように拡張することも可能です。以下の例では、システム通知とチャットメッセージの2種類のメッセージタイプを型で定 義しています。

複数メッセージタイプ対応の実装

// メッセージタイプの定義
interface SystemNotification {
  type: "system";
  message: string;
}
interface ChatMessage {
  type: "chat";
  sender: string;
  message: string;
}
type Message = SystemNotification | ChatMessage;
// Mediatorインターフェース
interface MessageMediator {
  sendMessage(message: Message, sender: MessageParticipant): void;
  addParticipant(participant: MessageParticipant): void;
}
// Colleagueインターフェース
interface MessageParticipant {
  receiveMessage(message: Message): void;
  getName(): string;
}
// ConcreteMediatorクラス
class MessageCenter implements MessageMediator {
  private participants: MessageParticipant[] = [];
  addParticipant(participant: MessageParticipant): void {
    this.participants.push(participant);
  }
  sendMessage(message: Message, sender: MessageParticipant): void {
    this.participants.forEach(participant => {
      if (participant !== sender) {
        participant.receiveMessage(message);
      }
    });
  }
}
// Colleagueクラス
class User implements MessageParticipant {
  private name: string;
  constructor(name: string) {
    this.name = name;
  }
  receiveMessage(message: Message): void {
    if (message.type === "system") {
      console.log(`${this.name} へシステム通知: ${message.message}`);
    } else if (message.type === "chat") {
      console.log(`${this.name} へチャットメッセージ: ${message.sender} から "${message.message}"`);
    }
  }
  getName(): string {
    return this.name;
  }
}

Message型を使うことで、システム通知とチャットメッセージを1つのメディエーターで扱いつつ、メッセージの構造が厳密に型定義されています。

使用例

const messageCenter = new MessageCenter();
const user1 = new User("Alice");
const user2 = new User("Bob");
messageCenter.addParticipant(user1);
messageCenter.addParticipant(user2);
messageCenter.sendMessage({ type: "system", message: "メンテナンスのお知らせ" }, user1);
messageCenter.sendMessage({ type: "chat", sender: "Alice", message: "こんにちは、Bob!" }, user1);

この実装により、異なる種類のメッセージも一貫した型で安全に扱えるようになります。

まとめ

TypeScriptでメディエーターパターンを型安全に実装することで、複雑なオブジェクト間通信を管理しやすくなります。インターフェースやジェネリクスを使ってメディエーターの通信内容を型で管理することで、通信ミスを防ぎつつ、柔軟で拡張性のあるコードを実現できるでしょう。メディエーターパターンは、オブジェクト間の直接的な依存関係を排除するため、保守性と可読性の高いコードに役立ちます。

Recommend