【TypeScript】メディエーターパターンの型定義 - 複雑なオブジェクト間通信を管理
2024-11-10
2024-11-10
TypeScriptとメディエーターパターンの型安全な実装
メディエーターパターンは、複数のオブジェクトが直接相互通信を行うのではなく、中央のメディエーター(仲介者)を通じて通信するように設計するデザインパターンです。TypeScript
でメディエーターパターンを実装する際には、インターフェースやジェネリクスを活用することで、通信を型安全に管理でき、複雑な依存関係を整理しつつ拡張性のあるコードを構築できます。
メディエーターパターンの基本構造
メディエーターパターンの主な構成要素は以下のとおりです。
- Mediator(メディエーター): 中央でオブジェクト間の通信を仲介するインターフェースやクラス。
- ConcreteMediator(具体的メディエーター):
Mediator
を実装し、オブジェクト間の通信ルールを定義。 - 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
でメディエーターパターンを型安全に実装することで、複雑なオブジェクト間通信を管理しやすくなります。インターフェースやジェネリクスを使ってメディエーターの通信内容を型で管理することで、通信ミスを防ぎつつ、柔軟で拡張性のあるコードを実現できるでしょう。メディエーターパターンは、オブジェクト間の直接的な依存関係を排除するため、保守性と可読性の高いコードに役立ちます。