開店の15分前は、食堂の中がいちばん静かな時間だ。
昨日の片付けを終えて椅子を床に下ろしていると、厨房の奥から出汁の香りがしてくる。シェフは今ごろ、昆布を浸したまま火加減を調べているころだろう。私はひとりでホールの椅子を並べながら、今日も今日とて何が持ち込まれるのかを考えていた。
引き戸が開いた。
「あ、まだ開店前ですけど」と私は振り返りながら言った。
入ってきたのは若い男性だった。二十代前半に見える。ノートパソコンを脇に抱えて、もう一方の手にはプリントアウトが数枚。引き戸の前で少し申し訳なさそうに止まった。
「すみません。早すぎましたか。外で待ちます」
「いえ、どうぞ。もうすぐシェフが来ますから、先に座っていてください」
彼はカウンターの端に座った。紙をカウンターの上に広げる。私は椅子を並べながら、ちらりと見てしまった。一番上の紙の先頭に、こう書いてあった。
| |
ファイル名は DeliveryOrder.pm。
私はもう少し目を留めた。デリバリーの注文を扱うクラスに、厨房の仕込み場・加熱場・盛り付け場の名前が、三行並んでいる。なんか、おかしい気がする。厨房の各部署を、注文ハンドラが全部知っている。ホール係の手帳に、厨房の内線番号が全部書いてあるような、落ち着かない感じ。
でもそれが正確にどういう問題なのかは、自分では言葉にできなかった。
シェフが厨房から出てきたのは、そこから数分後だった。手をエプロンで拭きながら、カウンターの彼を見る。
「早かったな」と言って、私のほうに目をやる。
私は少し緊張した。先に何かを言う機会は今しかないと思った。
「あの。さっきちょっと見てたんですが——デリバリーの注文を受けるクラスに、厨房のクラスが三つ直接書いてあります。なんか、おかしい気がして」
シェフは一拍、間を置いた。私のほうを見て、それから紙に目をやった。
「……ああ、見えてきたじゃないか」
低い声だった。驚かせるでもなく、褒めるでもなく、ただそれだけ。でも私にはそれで十分だった。合っていた。
彼が「あ、やっぱりそこなんですね」と少し前のめりになった。「Facade という名前を、先週技術ブログで見かけて。もしかしてこれかなと思って来ました」
シェフは彼の紙を手に取り、しばらく無言で読んだ。「まず事情を聞こう」と言って、コーヒーを出した。
この記事で学ぶこと
この記事は、「注文ハンドラが厨房の複数クラスを直接操作し、厨房側の変更のたびにハンドラを修正しなければならない」という問題を、Facadeパターンで整理する話です。複数のサブシステムを束ねる窓口クラスを一枚作ることで、なぜ呼び出し元が厨房の内部変更に影響されなくなるのかを、仕組みから解説します。
| 学ぶこと | ひとことで言うと |
|---|---|
| Facade(ファサード)パターン | 複数のサブシステムを束ね、シンプルな窓口メソッドだけを外に公開する構造パターン |
| Facade の語源 | 建物の「正面(外観)」。複雑な内部を一枚の壁で隠すイメージ |
| caller-knows-subsystem | 呼び出し元が複数のサブシステムクラスを直接知り、段取りの順番まで管理している状態 |
| サブシステム | Facade が束ねる内部のクラス群。今回でいえば Kitchen::Prep, Kitchen::Heat, Kitchen::Plate |
| 依存の方向 | どのクラスが何を知っているかの向き。Facade はこれを切り替えることで変更の波及を止める |
対象読者は、次のような人を想定しています。
- PerlとMooの基本(
has、new)がなんとなく分かる - 「呼び出し元のファイルに
use SomeClassが何行も並んでいて、一方が変わると全部直す」設計に引っかかりを覚えたことがある
技術スタックはPerl / Mooです。コードはすべて手元で動かし、テストが通ることを確認しています。本文中のモジュールは要点を抜き出して示しているため、実際にファイルへ保存するときは末尾に 1; を加えてください。
デリバリーが厨房を仕切っていた
彼が担当しているのは、飲食店とドライバーをつなぐデリバリーシステムの注文処理部分だ。注文が入ると DeliveryOrder クラスの execute が呼ばれ、料理を作ってドライバーに引き渡す流れになっている。
最初の設計は、こうだった。
| |
厨房のサブシステム群はそれぞれ独立したクラスだ。
| |
システムは動いていた。注文は通り、料理は仕上がり、ドライバーが受け取って届ける。問題はなかった——先週、全メニューにガーニッシュ(付け合わせ)を追加するまでは。
「厨房の人間が Kitchen::Garnish クラスを作りました」と彼は言った。「で、ガーニッシュを料理に添えるには、加熱のあとにガーニッシュ、それから盛り付けという順番になる。だから僕は DeliveryOrder を開いて、こう変えました」
| |
「直せました。でも——」と彼は続けた。「厨房側が Kitchen::Garnish を追加したのに、なんでデリバリーのコードを触らないといけないんだろうって。次また厨房が変わったら、また開くんだろうなと思って。それがずっと引っかかってて」
シェフはコーヒーカップを置いた。「よく気づいた。それが問題だ」
「ハンドラが厨房の段取りを仕切っている」
シェフは紙をカウンターの上に並べた。DeliveryOrder.pm の先頭を指さす。
「このファイルの先頭に、use Kitchen::Prep, use Kitchen::Heat, use Kitchen::Plate が並んでいる。つまり DeliveryOrder は、厨房の三つの部署を直接知っている。それぞれが何をするか、どの順番で呼ぶか——段取りを全部、自分で管理している」
私は配膳カウンターの端から、なんとなくその光景を想像した。ホール係が伝票を手に持ち、仕込み場に走って「仕込んでくれ」、次に加熱場に走って「焼いてくれ」、最後に盛り付け場に走って「盛り付けてくれ」と指示を出している。厨房の段取りを、ホール係が全部知って、全部自分で指揮している。
「でも——」と彼が言いかけた。
「ガーニッシュが増えたとき、何が起きたか」とシェフが先に言った。「厨房の変更なのに、お前(DeliveryOrder)を変えた。それが問題の正体だ」
シェフが言葉にした。私はそれが「caller-knows-subsystem」と呼ばれる状態だと後から知った。
呼び出し元がサブシステムを直接・網羅的に操作している状態——つまり、注文ハンドラが厨房の各クラスの存在と、それを呼ぶ順番を、自分で全部知っている。厨房の内部構造が、外に「漏れ出している」。厨房が変わるたびに、ハンドラが変わる。
「設計がスマートじゃない気がしてたのは、そういうことなんですね」と彼は言った。落ち着いた声だった。引っかかりが言葉を得た感じがした、と私には見えた。
「名前だけ知っていても、仕組みが分からなければ使えない」とシェフは言った。「Facade が何をするのか、実演しよう」
注文は一言で十分だ
シェフはホールのカウンターを一度見てから、厨房の入り口の受け渡し口を指した。「ここに、注文口をひとつ作る。ホール係はここに伝票を一枚渡すだけ。中で何をどの順番でやるかは、厨房が管理する。ホール係は知らなくていい」
それがFacadeだ。複数のサブシステムを束ね、シンプルな窓口メソッドだけを外に公開する構造パターン。
実装は Kitchen.pm という新しいクラスを一枚作ることから始まる。
| |
Kitchen が Kitchen::Prep, Kitchen::Heat, Kitchen::Plate を has で保持し、place_order の中で段取りをまとめる。外に公開するのは place_order だけだ。
そして DeliveryOrder はこうなる。
| |
use Kitchen::Prep, use Kitchen::Heat, use Kitchen::Plate の三行が消えた。代わりに use Kitchen の一行だけになった。execute の中の段取りも $self->_kitchen->place_order($item) の一行に変わった。配送の受付ログとドライバー手配は、そのまま DeliveryOrder の中に残っている——それは DeliveryOrder 固有の責務だからだ。
彼はBefore と After を並べて見た。それから顔を上げて言った。
「結局、place_order の中で仕込み→加熱→盛り付けと書いてますよね。Kitchen が全部知っているのは変わらない——誰かが知ってることには変わらないのでは?」
いい問いだと思った。私も同じことが頭にあった。
シェフはすぐには答えなかった。カウンターの受け渡し口をもう一度見てから、ゆっくり言った。
「そうだ。誰かが知らないといけない」
「だが、『誰が知るか』は選べる」
「Kitchen が知っていれば、DeliveryOrder は知らなくていい。知らないものは、中が変わっても影響を受けない。ガーニッシュがまた変わったとき——Kitchen だけを直せばいい。DeliveryOrder は何も変えなくていい。なぜか。DeliveryOrder は Kitchen の中を知らないからだ」
KitchenWithGarnish を作って確かめてみると、その通りだった。
| |
DeliveryOrder のコードは一行も変えずに、_kitchen に KitchenWithGarnish を渡すだけで動く。
| |
DeliveryOrder は KitchenWithGarnish という名前すら知らない。知っているのは place_order というメソッド名だけだ。
私は配膳カウンターの端をもう一度見た。伝票を一枚渡すだけのホール係と、受け取って中で全部やる厨房の受け渡し口。DeliveryOrder と Kitchen の関係は、そういうことだと思った。
「DeliveryOrder と厨房の間に、垣根ができた」と彼が言った。
「そういうことだ」とシェフは答えた。「それがFacadeだ」
Mermaid 図: Before と After の依存関係
Before(DeliveryOrder がサブシステムを直接知っている):
classDiagram
class DeliveryOrder {
+execute(item)
}
class Kitchen_Prep {
+prepare(item)
}
class Kitchen_Heat {
+cook(prepped)
}
class Kitchen_Plate {
+serve(cooked)
}
DeliveryOrder --> Kitchen_Prep
DeliveryOrder --> Kitchen_Heat
DeliveryOrder --> Kitchen_Plate
After(DeliveryOrder は Kitchen だけを知る):
classDiagram
class DeliveryOrder {
+execute(item)
}
class Kitchen {
+place_order(item)
}
class Kitchen_Prep {
+prepare(item)
}
class Kitchen_Heat {
+cook(prepped)
}
class Kitchen_Plate {
+serve(cooked)
}
DeliveryOrder --> Kitchen
Kitchen --> Kitchen_Prep
Kitchen --> Kitchen_Heat
Kitchen --> Kitchen_Plate
試食合格
テストを走らせた。
| |
全テスト通過、警告なし。
彼はAfterのコードを眺めた。DeliveryOrder.pm の先頭は use Kitchen の一行になっている。
「use Kitchen::Prep が、消えた」と彼は言った。
「ガーニッシュがまた変わっても、DeliveryOrder を開かなくていい」とシェフが言った。
「Kitchen だけ変える」
「そうだ」
彼は少し間を置いた。「来てよかった」と言った。大げさでなく、ただ事実を確かめた口調だった。
シェフの仕込み工程表
| 問題(調理ミス) | 技法(パターン) | 効果(仕上がり) |
|---|---|---|
注文ハンドラが厨房の複数クラスを直接 use し、段取りの順番まで自分で管理している(caller-knows-subsystem) | Facadeパターン:窓口クラスを一枚作り、複数サブシステムへの委譲と段取りをまとめる | 厨房側の変更が DeliveryOrder に波及しなくなる。呼び出し元は place_order だけを知ればよい |
工程
- 「呼び出し元のファイルに
use SomeSubsystemが複数行並んでいるか」を確認する - 呼び出し元がサブシステムを呼ぶ順番(段取り)をベタ書きしているコードを探す
- その「段取り」を引き受けるFacadeクラス(今回は
Kitchen.pm)を作り、hasでサブシステムを保持し、段取りをメソッドにまとめる - 呼び出し元の
useをFacadeクラスの一行に置き換え、段取り部分をFacadeメソッドの呼び出し一行に変える - テストを実行し、Before と After で処理結果が変わらないことを確認する
- 「サブシステム側に変更が起きたとき、呼び出し元を触らなくて済むか」を確認する
シェフより
「ホール係に厨房の仕込み方を全部覚えさせるな。厨房が変わるたびにホール係が覚え直すのは、仕事の分け方がおかしい。窓口を一つ作れば、中が変わっても外は変えなくていい。垣根はそのためにある」
彼を見送ってドアが閉まると、厨房からシェフが戻ってきた。私は片付けを続けながら、今日のことをもう一度なぞろうとした。
先に気づいた。合っていた。なぜかは、今もうまく言葉にできない。use Kitchen::Prep use Kitchen::Heat use Kitchen::Plate——注文を受けるクラスに、厨房の部署が並んでいた。それを見たとき、何かが落ち着かなかった。厨房の段取りを、注文ハンドラが知っていることへの違和感。
なぜおかしいのかは、シェフに説明してもらうまで言葉にできなかった。でも「おかしい」という感じは、先にあった。
誰かのコードを見て、先に「これ、おかしい気がします」と言えた。今日が初めてだった。
少し、こわいことだと思った。コードの匂いを嗅ぎ始めている、料理の人間が。
でも手応えがある。次の注文が来るのを、少しだけ待てる気がした。
