はじめに
皆さん、こんにちは!「JavaとPythonで比べるデザインパターン」シリーズの第19回目です。
今回は、オブジェクト間の複雑な相互作用を仲介し、疎結合を保つためのMediator(メディエーター)パターンについて解説します。
Mediatorパターンとは?
Mediatorパターンは、複数のオブジェクト(同僚、Colleagues)間の直接的な通信をカプセル化し、すべてを**仲介者(Mediator)**を介して行うようにする振る舞いパターンです。これにより、オブジェクト間の依存関係が減り、システムがより管理しやすくなります。
例えるなら、航空交通管制官です。
各パイロット(同僚)は、直接他のパイロットと通信するのではなく、管制官(メディエーター)を介して通信します。これにより、飛行機同士の衝突を防ぎ、複雑な空域の交通を円滑に管理できます。パイロットは「他の飛行機」について知る必要がなく、ただ「管制官」とだけやり取りすればよいのです。
このパターンの主な目的は以下の通りです。
- 複雑性の軽減: オブジェクト間の多対多の複雑な通信を、1対多のシンプルな通信に集約する
- 疎結合の実現: 各同僚オブジェクトが互いに依存しないようにし、システムの変更や拡張を容易にする
- 責任の集中: 通信ロジックをメディエーターに一元化し、各同僚オブジェクトの責務を単一に保つ
- 再利用性の向上: 同僚オブジェクトを異なるメディエーターと組み合わせて再利用できる
問題:直接通信による複雑性
まず、Mediatorパターンなしでオブジェクト間が直接通信する場合の問題を見てみましょう。
// 悪い例:直接通信による強結合
class Button {
private TextField textField;
private CheckBox checkBox;
private SubmitButton submitButton;
// 他のコンポーネントへの参照が必要
public Button(TextField textField, CheckBox checkBox, SubmitButton submitButton) {
this.textField = textField;
this.checkBox = checkBox;
this.submitButton = submitButton;
}
public void click() {
// 複雑な相互作用のロジック
if (textField.isEmpty()) {
submitButton.disable();
} else if (checkBox.isChecked()) {
submitButton.enable();
textField.setStyle("highlighted");
}
}
}
この方法では、各オブジェクトが他のオブジェクトを直接知っている必要があり、変更時の影響範囲が大きくなります。
Javaでの実装:厳格なインターフェースとクラス
Javaは、メディエーターとコンポーネントの役割を明確に定義することで、このパターンを厳格に実装できます。
このパターンでは、以下のコンポーネントが登場します。
- メディエーターインターフェース(Mediator): 同僚オブジェクトと通信するためのメソッドを定義します
- 具象メディエータークラス(ConcreteMediator): 仲介者インターフェースを実装し、同僚オブジェクト間の通信を調整するロジックを保持します
- 同僚抽象クラス(Colleague): 仲介者への参照を保持する抽象クラス
- 具象同僚クラス(ConcreteColleague): 同僚抽象クラスを継承し、メディエーターを介して通信するクラス
例1: チャットルームシステム
// JavaでのMediatorパターンの実装例
import java.util.ArrayList;
import java.util.List;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
// 1. メディエーターインターフェース
interface ChatRoomMediator {
void sendMessage(String message, User user);
void sendPrivateMessage(String message, User sender, String recipientName);
void addUser(User user);
void removeUser(User user);
void notifyUserJoined(User user);
void notifyUserLeft(User user);
}
// 2. 具象メディエータークラス
class ChatRoom implements ChatRoomMediator {
private List<User> users = new ArrayList<>();
private List<String> chatHistory = new ArrayList<>();
@Override
public void addUser(User user) {
users.add(user);
notifyUserJoined(user);
// 新規ユーザーに最近のメッセージ履歴を送信
sendRecentHistory(user);
}
@Override
public void removeUser(User user) {
users.remove(user);
notifyUserLeft(user);
}
@Override
public void sendMessage(String message, User sender) {
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm"));
String formattedMessage = String.format("[%s] %s: %s", timestamp, sender.getName(), message);
chatHistory.add(formattedMessage);
for (User user : users) {
if (user != sender) {
user.receiveMessage(formattedMessage);
}
}
}
@Override
public void sendPrivateMessage(String message, User sender, String recipientName) {
User recipient = findUserByName(recipientName);
if (recipient != null) {
String privateMessage = String.format("[Private from %s]: %s", sender.getName(), message);
recipient.receiveMessage(privateMessage);
sender.receiveMessage(String.format("[Private to %s]: %s", recipientName, message));
} else {
sender.receiveMessage("User " + recipientName + " not found.");
}
}
@Override
public void notifyUserJoined(User user) {
String notification = user.getName() + " has joined the chat room.";
for (User u : users) {
if (u != user) {
u.receiveNotification(notification);
}
}
}
@Override
public void notifyUserLeft(User user) {
String notification = user.getName() + " has left the chat room.";
for (User u : users) {
u.receiveNotification(notification);
}
}
private User findUserByName(String name) {
return users.stream()
.filter(user -> user.getName().equals(name))
.findFirst()
.orElse(null);
}
private void sendRecentHistory(User user) {
int historySize = Math.min(5, chatHistory.size());
for (int i = chatHistory.size() - historySize; i < chatHistory.size(); i++) {
user.receiveMessage("[History] " + chatHistory.get(i));
}
}
}
// 3. 同僚抽象クラス
abstract class User {
protected ChatRoomMediator mediator;
protected String name;
public User(ChatRoomMediator mediator, String name) {
this.mediator = mediator;
this.name = name;
}
public String getName() {
return name;
}
public abstract void sendMessage(String message);
public abstract void sendPrivateMessage(String message, String recipientName);
public abstract void receiveMessage(String message);
public abstract void receiveNotification(String notification);
}
// 4. 具象同僚クラス
class ChatUser extends User {
public ChatUser(ChatRoomMediator mediator, String name) {
super(mediator, name);
}
@Override
public void sendMessage(String message) {
System.out.println(name + " sends: " + message);
mediator.sendMessage(message, this);
}
@Override
public void sendPrivateMessage(String message, String recipientName) {
System.out.println(name + " sends private message to " + recipientName + ": " + message);
mediator.sendPrivateMessage(message, this, recipientName);
}
@Override
public void receiveMessage(String message) {
System.out.println(name + " receives: " + message);
}
@Override
public void receiveNotification(String notification) {
System.out.println(name + " notification: " + notification);
}
}
// 使い方
public class ChatRoomExample {
public static void main(String[] args) {
ChatRoomMediator chatRoom = new ChatRoom();
User alice = new ChatUser(chatRoom, "Alice");
User bob = new ChatUser(chatRoom, "Bob");
User charlie = new ChatUser(chatRoom, "Charlie");
chatRoom.addUser(alice);
chatRoom.addUser(bob);
chatRoom.addUser(charlie);
System.out.println("\n--- Starting chat ---");
alice.sendMessage("Hi everyone!");
bob.sendMessage("Hello Alice!");
System.out.println("\n--- Private message ---");
alice.sendPrivateMessage("How are you?", "Bob");
System.out.println("\n--- User leaving ---");
chatRoom.removeUser(charlie);
}
}
例2: GUIコンポーネントの連携
// GUIコンポーネントのメディエーター例
interface DialogMediator {
void notify(Component sender, String event);
}
abstract class Component {
protected DialogMediator mediator;
public Component(DialogMediator mediator) {
this.mediator = mediator;
}
}
class Button extends Component {
public Button(DialogMediator mediator) {
super(mediator);
}
public void click() {
System.out.println("Button clicked");
mediator.notify(this, "click");
}
}
class TextField extends Component {
private String text = "";
public TextField(DialogMediator mediator) {
super(mediator);
}
public void setText(String text) {
this.text = text;
mediator.notify(this, "textChanged");
}
public String getText() {
return text;
}
public boolean isEmpty() {
return text.trim().isEmpty();
}
}
class CheckBox extends Component {
private boolean checked = false;
public CheckBox(DialogMediator mediator) {
super(mediator);
}
public void setChecked(boolean checked) {
this.checked = checked;
mediator.notify(this, "checkChanged");
}
public boolean isChecked() {
return checked;
}
}
class LoginDialog implements DialogMediator {
private TextField usernameField;
private TextField passwordField;
private CheckBox rememberMeBox;
private Button loginButton;
public LoginDialog() {
this.usernameField = new TextField(this);
this.passwordField = new TextField(this);
this.rememberMeBox = new CheckBox(this);
this.loginButton = new Button(this);
}
@Override
public void notify(Component sender, String event) {
if (sender == usernameField || sender == passwordField) {
validateFields();
} else if (sender == rememberMeBox) {
System.out.println("Remember me: " + rememberMeBox.isChecked());
} else if (sender == loginButton) {
performLogin();
}
}
private void validateFields() {
boolean isValid = !usernameField.isEmpty() && !passwordField.isEmpty();
System.out.println("Login button enabled: " + isValid);
}
private void performLogin() {
System.out.println("Logging in user: " + usernameField.getText());
if (rememberMeBox.isChecked()) {
System.out.println("Saving user preferences");
}
}
// Getter methods for testing
public TextField getUsernameField() { return usernameField; }
public TextField getPasswordField() { return passwordField; }
public CheckBox getRememberMeBox() { return rememberMeBox; }
public Button getLoginButton() { return loginButton; }
}
Pythonでの実装:シンプルなクラスと動的な通信
Pythonでは、より柔軟で動的なアプローチでMediatorパターンを実装できます。
# PythonでのMediatorパターンの実装例
from datetime import datetime
from typing import List, Optional
from abc import ABC, abstractmethod
# 1 & 2. メディエーターの基底クラスと具象実装
class ChatRoomMediator(ABC):
@abstractmethod
def send_message(self, message: str, sender: 'User') -> None:
pass
@abstractmethod
def add_user(self, user: 'User') -> None:
pass
class ChatRoom(ChatRoomMediator):
def __init__(self):
self._users: List['User'] = []
self._chat_history: List[str] = []
self._banned_words = ['spam', 'bad_word'] # 禁止ワード
def add_user(self, user: 'User') -> None:
self._users.append(user)
user.set_chat_room(self)
self._notify_user_joined(user)
self._send_recent_history(user)
def remove_user(self, user: 'User') -> None:
if user in self._users:
self._users.remove(user)
self._notify_user_left(user)
def send_message(self, message: str, sender: 'User') -> None:
# メッセージフィルタリング
if self._is_message_allowed(message):
timestamp = datetime.now().strftime("%H:%M")
formatted_message = f"[{timestamp}] {sender.name}: {message}"
self._chat_history.append(formatted_message)
for user in self._users:
if user != sender:
user.receive_message(formatted_message)
else:
sender.receive_notification("Message contains banned words and was not sent.")
def send_private_message(self, message: str, sender: 'User', recipient_name: str) -> None:
recipient = self._find_user_by_name(recipient_name)
if recipient:
private_message = f"[Private from {sender.name}]: {message}"
recipient.receive_message(private_message)
sender.receive_message(f"[Private to {recipient_name}]: {message}")
else:
sender.receive_notification(f"User {recipient_name} not found.")
def _notify_user_joined(self, user: 'User') -> None:
notification = f"{user.name} has joined the chat room."
for u in self._users:
if u != user:
u.receive_notification(notification)
def _notify_user_left(self, user: 'User') -> None:
notification = f"{user.name} has left the chat room."
for u in self._users:
u.receive_notification(notification)
def _find_user_by_name(self, name: str) -> Optional['User']:
return next((user for user in self._users if user.name == name), None)
def _send_recent_history(self, user: 'User') -> None:
recent_messages = self._chat_history[-5:] # 最新5件
for msg in recent_messages:
user.receive_message(f"[History] {msg}")
def _is_message_allowed(self, message: str) -> bool:
return not any(banned_word in message.lower() for banned_word in self._banned_words)
# 3 & 4. ユーザークラス(同僚オブジェクト)
class User:
def __init__(self, name: str):
self.name = name
self._chat_room: Optional[ChatRoomMediator] = None
def set_chat_room(self, chat_room: ChatRoomMediator) -> None:
self._chat_room = chat_room
def send_message(self, message: str) -> None:
print(f"{self.name} sends: {message}")
if self._chat_room:
self._chat_room.send_message(message, self)
def send_private_message(self, message: str, recipient_name: str) -> None:
print(f"{self.name} sends private message to {recipient_name}: {message}")
if self._chat_room:
self._chat_room.send_private_message(message, self, recipient_name)
def receive_message(self, message: str) -> None:
print(f"{self.name} receives: {message}")
def receive_notification(self, notification: str) -> None:
print(f"{self.name} notification: {notification}")
# より高度な例:異なるタイプのユーザー
class AdminUser(User):
def __init__(self, name: str):
super().__init__(name)
def kick_user(self, user_name: str) -> None:
if self._chat_room and hasattr(self._chat_room, '_find_user_by_name'):
user_to_kick = self._chat_room._find_user_by_name(user_name)
if user_to_kick:
self._chat_room.remove_user(user_to_kick)
print(f"Admin {self.name} kicked {user_name}")
else:
print(f"User {user_name} not found")
class BotUser(User):
def __init__(self, name: str):
super().__init__(name)
self._responses = {
"hello": "Hi there! How can I help you?",
"help": "Available commands: hello, help, time",
"time": f"Current time is {datetime.now().strftime('%H:%M')}"
}
def receive_message(self, message: str) -> None:
super().receive_message(message)
# 簡単な自動応答
if ":" in message:
actual_message = message.split(":", 2)[-1].strip().lower()
if actual_message in self._responses:
response = self._responses[actual_message]
# 少し遅延してから応答(実際のボットっぽく)
print(f"[Bot auto-response] {self.name} sends: {response}")
if self._chat_room:
self._chat_room.send_message(response, self)
# 使い方
def main():
chat_room = ChatRoom()
# 異なるタイプのユーザーを作成
alice = User("Alice")
bob = User("Bob")
admin = AdminUser("Admin")
bot = BotUser("HelpBot")
# チャットルームにユーザーを追加
chat_room.add_user(alice)
chat_room.add_user(bob)
chat_room.add_user(admin)
chat_room.add_user(bot)
print("\n--- Starting chat ---")
alice.send_message("hello") # ボットが反応
print("\n--- Private messages ---")
alice.send_private_message("How are you?", "Bob")
print("\n--- Admin action ---")
admin.kick_user("Bob")
print("\n--- Banned word test ---")
alice.send_message("This is spam message") # フィルタリングされる
if __name__ == "__main__":
main()
Pythonでのより関数型なアプローチ
# 関数型のアプローチを取り入れたメディエーター
from typing import Callable, Dict, List
from dataclasses import dataclass
from enum import Enum
class EventType(Enum):
MESSAGE_SENT = "message_sent"
USER_JOINED = "user_joined"
USER_LEFT = "user_left"
@dataclass
class Event:
type: EventType
data: dict
sender: str
class EventMediator:
def __init__(self):
self._handlers: Dict[EventType, List[Callable]] = {event_type: [] for event_type in EventType}
def subscribe(self, event_type: EventType, handler: Callable) -> None:
self._handlers[event_type].append(handler)
def publish(self, event: Event) -> None:
for handler in self._handlers[event.type]:
handler(event)
# 使用例
mediator = EventMediator()
def log_handler(event: Event):
print(f"LOG: {event.type.value} - {event.data}")
def notification_handler(event: Event):
if event.type == EventType.MESSAGE_SENT:
print(f"NOTIFY: New message from {event.sender}")
mediator.subscribe(EventType.MESSAGE_SENT, log_handler)
mediator.subscribe(EventType.MESSAGE_SENT, notification_handler)
# イベント発行
event = Event(EventType.MESSAGE_SENT, {"message": "Hello"}, "Alice")
mediator.publish(event)
実用的な応用例
1. MVC アーキテクチャでのController
# MVCパターンでのMediatorの活用
class ModelViewMediator:
def __init__(self):
self._models = {}
self._views = {}
def register_model(self, name: str, model):
self._models[name] = model
model.set_mediator(self)
def register_view(self, name: str, view):
self._views[name] = view
view.set_mediator(self)
def notify_model_changed(self, model_name: str, data):
# モデルの変更をすべてのビューに通知
for view in self._views.values():
view.update_from_model(model_name, data)
def notify_view_action(self, action: str, data):
# ビューのアクションを適切なモデルに転送
if action == "save_user" and "user" in self._models:
self._models["user"].save(data)
2. ワークフローシステム
// ワークフローのメディエーター例
interface WorkflowMediator {
void processStep(String stepName, Object data);
void completeWorkflow();
}
class OrderProcessingWorkflow implements WorkflowMediator {
private PaymentService paymentService;
private InventoryService inventoryService;
private ShippingService shippingService;
// 各ステップの処理を調整
@Override
public void processStep(String stepName, Object data) {
switch (stepName) {
case "payment":
processPayment((PaymentInfo) data);
break;
case "inventory":
reserveInventory((OrderInfo) data);
break;
case "shipping":
scheduleShipping((ShippingInfo) data);
break;
}
}
private void processPayment(PaymentInfo paymentInfo) {
// 支払い処理後、在庫確保に進む
if (paymentService.processPayment(paymentInfo)) {
processStep("inventory", paymentInfo.getOrderInfo());
}
}
}
パターンの利点と欠点
利点
- 疎結合: オブジェクト間の直接的な依存関係を排除
- 集中管理: 複雑な通信ロジックを一箇所に集約
- 拡張性: 新しい同僚オブジェクトの追加が容易
- 再利用性: 同僚オブジェクトを異なる文脈で再利用可能
- テスト容易性: メディエーターをモック化してテストが簡単
欠点
- 複雑性の移転: メディエーター自体が複雑になる可能性
- パフォーマンス: 全ての通信がメディエーターを経由するためのオーバーヘッド
- 単一障害点: メディエーターに問題が発生すると全体に影響
- 過度の抽象化: 単純なシステムには不必要な複雑さを追加
まとめ:本質は「集中管理による疎結合」
特性 | Java | Python |
---|---|---|
主な解決策 | 明示的なインターフェースとクラス階層 | 柔軟なクラス設計とイベント駆動 |
設計思想 | 複雑な通信ロジックを単一のクラスに集中させる | オブジェクトの責務を最小限に保ち、通信を仲介者に任せる |
実装スタイル | 型を通じて役割を厳密に定義 | 動的な特性を活かした柔軟な実装 |
拡張性 | インターフェースによる厳格な契約 | ダックタイピングによる柔軟な拡張 |
Mediatorパターンは、 「多対多の複雑な通信を1対多に単純化する」 という本質を持ちます。両言語とも基本的な実装パターンは似ていますが、Javaは型安全性と明確な役割分担を重視し、Pythonはより動的で柔軟なアプローチを取ります。
このパターンは、GUIアプリケーション、チャットシステム、ワークフローエンジン、MVCアーキテクチャなど、複数のコンポーネントが協調する必要があるシステムで特に威力を発揮します。
次回は、アルゴリズムをカプセル化し、実行時に切り替えるStrategyパターンについて解説します。お楽しみに!
次回のテーマは、「Day 20 Strategyパターン:アルゴリズムを動的に切り替える」です。