Mediatorパターン
概要
相互に影響し合うオブジェクトを仲介者(Mediator)を介して疎結合にするパターン。
仲介者を用意することで、オブジェクト同士がお互いの存在を知らなくてもよくなる。
そのことで依存オブジェクトを増やしやすくなる
使い方
一番代表的な例のメッセージの送受信を例にする。
bad
type Message = {
owner: MessageUser;
content: string;
};
class MessageUser {
messages: Message[] = [];
constructor(public name: string) {}
sendMessage(message: string, to: MessageUser): {
this.messages.push({ owner: this, content: message });
to.receiveMessage(message, this);
}
receiveMessage(message: string, from: MessageUser): {
this.messages.push({ owner: from, content: message });
}
}
const alice = new MessageUser("Alice");
const bob = new MessageUser("Bob");
const message = "Hello, Bob!";
// 2人の時ならいいが...
alice.sendMessage(message, bob);
const charlie = new MessageUser("Charlie");
// 3人目が増えたら、送信先を増やす必要がある
alice.sendMessage(message, bob);
alice.sendMessage(message, charlie);
チャットルームの人数が増えたときの送信処理が増えてしまう。
その上、送信処理の共通ルールが変わったときに、全てのsendMessageを修正する必要がある。
good
// mediator
class ChatRoom {
private users: MessageUser[] = [];
addUsers(users: MessageUser[]) {}
sendMessage(message: string, from: MessageUser) {
this.users.forEach((user) => {
if (user !== from) {
user.receiveMessage(message, from);
}
});
}
}
type Message = {
owner: MessageUser;
content: string;
};
class MessageUser {
messages: Message[] = [];
constructor(public name: string, private room: ChatRoom) {}
sendMessage(message: string): void {
this.messages.push({ owner: this, content: message });
chat.sendMessage(message, this);
}
receiveMessage(message: string, from: MessageUser): void {
this.messages.push({ owner: from, content: message });
}
}
const chat = new ChatRoom();
const alice = new MessageUser("Alice", chat);
const bob = new MessageUser("Bob", chat);
const charlie = new MessageUser("Charlie", chat);
alice.sendMessage("Hello, everyone!");
chatというmediaotorを媒介させることによってaliceは誰にメッセージを送るのかを気にしなくていい。
人数分のsendMessageを呼ぶ必要がなくなる。
代表例2: フォーム
メッセージ以外にも代表例があります
bad
const Button = ({ onClick, canSubmit }: { onClick: () => void; canSubmit: boolean }) => {
const handleClick = () => {
if (canSubmit) {
onClick();
}
};
return <button onClick={handleClick}>Submit</button>;
};
const EmailInput = (
{
value,
onChange,
setCanSubmit
}: {
value: string;
onChange: (value: string) => void;
}
) => {
const isValidEmail = (email: string) => {
// 簡易的なメールアドレスのバリデーション
return /\S+@\S+\.\S+/.test(email);
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.value;
onChange(newValue);
setCanSubmit(isValidEmail(newValue));
};
return <input type="email" value={value} onChange={(e) => handleChange(e.target.value)} />;
};
const form = () => {
const [email, setEmail] = useState("");
const [canSubmit, setCanSubmit] = useState(false);
const handleSubmit = () => {
console.log("Submitted:", email);
};
return (
<div>
<EmailInput value={email} onChange={setEmail} setCanSubmit={setCanSubmit} />
<Button onClick={handleSubmit} canSubmit={canSubmit} />
</div>
);
};
Buttonのclickの状態管理をそれぞれのフォームで管理すると、フォームが増えるとButtonの状態を管理するためのコードが増えてしまう上に、整合性が取れなくなる。
good
const Button = ({ onClick, canSubmit }: { onClick: () => void; canSubmit: boolean }) => {
const handleClick = () => {
if (canSubmit) {
onClick();
}
};
return <button onClick={handleClick}>Submit</button>;
};
const EmailInput = (
{ value, onChange, }: { value: string; onChange: (value: string) => void; }
) => {
return <input type="email" value={value} onChange={(e) => onChange(e.target.value)} />;
};
const PasswordInput = (
{ value, onChange, }: { value: string; onChange: (value: string) => void; }
) => {
return <input type="password" value={value} onChange={(e) => onChange(e.target.value)} />;
};
const form = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const isValidEmail = () => {
// 簡易的なメールアドレスのバリデーション
return /\S+@\S+\.\S+/.test(email);
};
const isValidPassword = () => {
return password.length >= 6; // 簡易的なパスワードのバリデーション
};
const canSubmit = isValidEmail(email) && isValidPassword(password);
const handleSubmit = () => {
console.log("Submitted:", { email, password });
};
return (
<div>
<EmailInput value={email} onChange={setEmail} />
<PasswordInput value={password} onChange={setPassword} />
<Button onClick={handleSubmit} canSubmit={canSubmit} />
</div>
);
};
formをmediatorとして扱い、Buttonの送信可能状態の管理を一括管理でき、入力欄を増やしやすくなる
使い所
- 複数のオブジェクトが協調して相互作用する時
- 複数のオブジェクトの共通のルールを定義したいとき