中央のメディエータオブジェクトを介してコンポーネント間のやり取りを処理する
メディエータ・ミドルウェアパターン
メディエータパターン (mediator pattern) は、中央にあるメディエータという存在を通してコンポーネントどうしがやり取りすることを可能にします。コンポーネントどうしが直接対話するのではなく、メディエータがリクエストを受け取り、それを転送します。JavaScript では、メディエータは単なるオブジェクトリテラルや関数であることが多いです。
このパターンを、航空管制官とパイロットとの関係に例えることができます。パイロットどうしが直接会話するのではなく (これはおそらくかなりカオスな状況となります)、パイロットは管制官と会話します。管制官は、飛行機どうしが衝突することなく安全に飛行できるよう、各飛行機が必要な情報を受け取ることができるようにするのです。
JavaScript で飛行機をコントロールすることがないといいのですが、私たちはしばしば、オブジェクト間で多方向に行き交うデータを扱わなければなりません。コンポーネントの数が多くなると、コンポーネント間のやり取りはかなり複雑なものとなるはずです。
そこで、各オブジェクトが他のオブジェクトと直接やり取りし、多対多の関係となる代わりに、オブジェクトのリクエストをメディエータが処理するようにします。メディエータはこのリクエストを処理し、必要なところへ送ります。
メディエータパターンのわかりやすい例としてはチャットルームがあります。チャットルームのユーザーは、お互いに直接話すことはありません。その代わり、チャットルームサーバーがメディエータとしてユーザーを仲介します。
class ChatRoom {
logMessage(user, message) {
const time = new Date();
const sender = user.getName();
console.log(`${time} [${sender}]: ${message}`);
}
}
class User {
constructor(name, chatroom) {
this.name = name;
this.chatroom = chatroom;
}
getName() {
return this.name;
}
send(message) {
this.chatroom.logMessage(this, message);
}
}
チャットルームに接続するユーザーを新規に作成しましょう。各ユーザーインスタンスは send
メソッドをもっており、これを用いてメッセージを送信することができます。
class ChatRoom {
logMessage(user, message) {
const sender = user.getName();
console.log(`${new Date().toLocaleString()} [${sender}]: ${message}`);
}
}
class User {
constructor(name, chatroom) {
this.name = name;
this.chatroom = chatroom;
}
getName() {
return this.name;
}
send(message) {
this.chatroom.logMessage(this, message);
}
}
const chatroom = new ChatRoom();
const user1 = new User("John Doe", chatroom);
const user2 = new User("Jane Doe", chatroom);
user1.send("Hi there!");
user2.send("Hey!");
ケーススタディ
Express.js は、Web アプリケーションサーバーのフレームワークとして有名です。ユーザーがアクセスする route にコールバックを追加することができます。
たとえば、ユーザーがルート /
にアクセスした場合に、リクエストにあるヘッダーを追加したいとします。ミドルウェアのコールバックにおいてこのヘッダーを追加することができます。
const app = require("express")();
app.use("/", (req, res, next) => {
req.headers["test-header"] = 1234;
next();
});
next
メソッドは、リクエストとレスポンスのサイクルの中にある、次のコールバックを呼び出します。このようにして、リクエストとレスポンスの間にミドルウェア関数の連鎖を作ることができます。
test-header
が正しく追加されたかどうかをチェックする、別のミドルウェア関数を追加してみましょう。直前のミドルウェア関数で加えられた変更は、この連鎖の中にいれば確認することができます。
const app = require("express")();
app.use(
"/",
(req, res, next) => {
req.headers["test-header"] = 1234;
next();
},
(req, res, next) => {
console.log(`Request has test header: ${!!req.headers["test-header"]}`);
next();
}
);
完璧です!ミドルウェア関数を通じて、リクエストオブジェクトがレスポンスへと至るまでのあいだに、これを 追跡・ 変更することができるのです。
const app = require("express")();
const html = require("./data");
app.use(
"/",
(req, res, next) => {
req.headers["test-header"] = 1234;
next();
},
(req, res, next) => {
console.log(`Request has test header: ${!!req.headers["test-header"]}`);
next();
}
);
app.get("/", (req, res) => {
res.set("Content-Type", "text/html");
res.send(Buffer.from(html));
});
app.listen(8080, function() {
console.log("Server is running on 8080");
});
ユーザーがルートエンドポイント /
にアクセスするたびに、 2 つのミドルウェアコールバックが呼び出されます。
メディエータパターンは、すべての通信が中央のメディエータを経由して流れるようにすることで、オブジェクト間の多対多の関係を単純化することができます。