Featured image of post コードシェフの仕込み帳【Facade】注文は一言で十分だ〜注文ハンドラが厨房の段取りを全部知っているコードを、窓口クラス一枚で整理する〜

コードシェフの仕込み帳【Facade】注文は一言で十分だ〜注文ハンドラが厨房の段取りを全部知っているコードを、窓口クラス一枚で整理する〜

注文ハンドラが厨房の仕込み・加熱・盛り付けを直接呼ぶ密結合なコードを、Facadeパターンで整理します。PerlとMooで「窓口クラス一枚」を設け、呼び出し元が厨房の内部変更に影響されない設計へ。仕組みから丁寧に解説します。

開店の15分前は、食堂の中がいちばん静かな時間だ。

昨日の片付けを終えて椅子を床に下ろしていると、厨房の奥から出汁の香りがしてくる。シェフは今ごろ、昆布を浸したまま火加減を調べているころだろう。私はひとりでホールの椅子を並べながら、今日も今日とて何が持ち込まれるのかを考えていた。

引き戸が開いた。

「あ、まだ開店前ですけど」と私は振り返りながら言った。

入ってきたのは若い男性だった。二十代前半に見える。ノートパソコンを脇に抱えて、もう一方の手にはプリントアウトが数枚。引き戸の前で少し申し訳なさそうに止まった。

「すみません。早すぎましたか。外で待ちます」

「いえ、どうぞ。もうすぐシェフが来ますから、先に座っていてください」

彼はカウンターの端に座った。紙をカウンターの上に広げる。私は椅子を並べながら、ちらりと見てしまった。一番上の紙の先頭に、こう書いてあった。

1
2
3
use Kitchen::Prep;
use Kitchen::Heat;
use Kitchen::Plate;

ファイル名は DeliveryOrder.pm

私はもう少し目を留めた。デリバリーの注文を扱うクラスに、厨房の仕込み場・加熱場・盛り付け場の名前が、三行並んでいる。なんか、おかしい気がする。厨房の各部署を、注文ハンドラが全部知っている。ホール係の手帳に、厨房の内線番号が全部書いてあるような、落ち着かない感じ。

でもそれが正確にどういう問題なのかは、自分では言葉にできなかった。

シェフが厨房から出てきたのは、そこから数分後だった。手をエプロンで拭きながら、カウンターの彼を見る。

「早かったな」と言って、私のほうに目をやる。

私は少し緊張した。先に何かを言う機会は今しかないと思った。

「あの。さっきちょっと見てたんですが——デリバリーの注文を受けるクラスに、厨房のクラスが三つ直接書いてあります。なんか、おかしい気がして」

シェフは一拍、間を置いた。私のほうを見て、それから紙に目をやった。

「……ああ、見えてきたじゃないか」

低い声だった。驚かせるでもなく、褒めるでもなく、ただそれだけ。でも私にはそれで十分だった。合っていた。

彼が「あ、やっぱりそこなんですね」と少し前のめりになった。「Facade という名前を、先週技術ブログで見かけて。もしかしてこれかなと思って来ました」

シェフは彼の紙を手に取り、しばらく無言で読んだ。「まず事情を聞こう」と言って、コーヒーを出した。

この記事で学ぶこと

この記事は、「注文ハンドラが厨房の複数クラスを直接操作し、厨房側の変更のたびにハンドラを修正しなければならない」という問題を、Facadeパターンで整理する話です。複数のサブシステムを束ねる窓口クラスを一枚作ることで、なぜ呼び出し元が厨房の内部変更に影響されなくなるのかを、仕組みから解説します。

学ぶことひとことで言うと
Facade(ファサード)パターン複数のサブシステムを束ね、シンプルな窓口メソッドだけを外に公開する構造パターン
Facade の語源建物の「正面(外観)」。複雑な内部を一枚の壁で隠すイメージ
caller-knows-subsystem呼び出し元が複数のサブシステムクラスを直接知り、段取りの順番まで管理している状態
サブシステムFacade が束ねる内部のクラス群。今回でいえば Kitchen::Prep, Kitchen::Heat, Kitchen::Plate
依存の方向どのクラスが何を知っているかの向き。Facade はこれを切り替えることで変更の波及を止める

対象読者は、次のような人を想定しています。

  • PerlとMooの基本(hasnew)がなんとなく分かる
  • 「呼び出し元のファイルに use SomeClass が何行も並んでいて、一方が変わると全部直す」設計に引っかかりを覚えたことがある

技術スタックはPerl / Mooです。コードはすべて手元で動かし、テストが通ることを確認しています。本文中のモジュールは要点を抜き出して示しているため、実際にファイルへ保存するときは末尾に 1; を加えてください。

デリバリーが厨房を仕切っていた

彼が担当しているのは、飲食店とドライバーをつなぐデリバリーシステムの注文処理部分だ。注文が入ると DeliveryOrder クラスの execute が呼ばれ、料理を作ってドライバーに引き渡す流れになっている。

最初の設計は、こうだった。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package DeliveryOrder;
use Moo;
use v5.36;
use Kitchen::Prep;
use Kitchen::Heat;
use Kitchen::Plate;

has _prep  => (is => 'lazy', default => sub { Kitchen::Prep->new });
has _heat  => (is => 'lazy', default => sub { Kitchen::Heat->new });
has _plate => (is => 'lazy', default => sub { Kitchen::Plate->new });

sub _log_received    { }   # 受付ログ
sub _dispatch_driver { }   # ドライバー手配

sub execute {
    my ($self, $item) = @_;
    $self->_log_received($item);
    my $prepped = $self->_prep->prepare($item);
    my $cooked  = $self->_heat->cook($prepped);
    my $dish    = $self->_plate->serve($cooked);
    $self->_dispatch_driver($dish);
    return $dish;
}

厨房のサブシステム群はそれぞれ独立したクラスだ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package Kitchen::Prep;
use Moo;
use v5.36;
sub prepare { my ($self, $item) = @_; "$item(仕込み済み)" }

package Kitchen::Heat;
use Moo;
use v5.36;
sub cook { my ($self, $prepped) = @_; "$prepped(加熱済み)" }

package Kitchen::Plate;
use Moo;
use v5.36;
sub serve { my ($self, $cooked) = @_; "$cooked(盛り付け済み)" }

システムは動いていた。注文は通り、料理は仕上がり、ドライバーが受け取って届ける。問題はなかった——先週、全メニューにガーニッシュ(付け合わせ)を追加するまでは。

「厨房の人間が Kitchen::Garnish クラスを作りました」と彼は言った。「で、ガーニッシュを料理に添えるには、加熱のあとにガーニッシュ、それから盛り付けという順番になる。だから僕は DeliveryOrder を開いて、こう変えました」

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use Kitchen::Garnish;   # ← 追加

has _garnish => (is => 'lazy', default => sub { Kitchen::Garnish->new });

sub execute {
    my ($self, $item) = @_;
    $self->_log_received($item);
    my $prepped   = $self->_prep->prepare($item);
    my $cooked    = $self->_heat->cook($prepped);
    my $garnished = $self->_garnish->garnish($cooked);   # ← 追加
    my $dish      = $self->_plate->serve($garnished);
    $self->_dispatch_driver($dish);
    return $dish;
}

「直せました。でも——」と彼は続けた。「厨房側が Kitchen::Garnish を追加したのに、なんでデリバリーのコードを触らないといけないんだろうって。次また厨房が変わったら、また開くんだろうなと思って。それがずっと引っかかってて」

シェフはコーヒーカップを置いた。「よく気づいた。それが問題だ」

「ハンドラが厨房の段取りを仕切っている」

シェフは紙をカウンターの上に並べた。DeliveryOrder.pm の先頭を指さす。

「このファイルの先頭に、use Kitchen::Prep, use Kitchen::Heat, use Kitchen::Plate が並んでいる。つまり DeliveryOrder は、厨房の三つの部署を直接知っている。それぞれが何をするか、どの順番で呼ぶか——段取りを全部、自分で管理している」

私は配膳カウンターの端から、なんとなくその光景を想像した。ホール係が伝票を手に持ち、仕込み場に走って「仕込んでくれ」、次に加熱場に走って「焼いてくれ」、最後に盛り付け場に走って「盛り付けてくれ」と指示を出している。厨房の段取りを、ホール係が全部知って、全部自分で指揮している。

「でも——」と彼が言いかけた。

「ガーニッシュが増えたとき、何が起きたか」とシェフが先に言った。「厨房の変更なのに、お前(DeliveryOrder)を変えた。それが問題の正体だ」

シェフが言葉にした。私はそれが「caller-knows-subsystem」と呼ばれる状態だと後から知った。

呼び出し元がサブシステムを直接・網羅的に操作している状態——つまり、注文ハンドラが厨房の各クラスの存在と、それを呼ぶ順番を、自分で全部知っている。厨房の内部構造が、外に「漏れ出している」。厨房が変わるたびに、ハンドラが変わる。

「設計がスマートじゃない気がしてたのは、そういうことなんですね」と彼は言った。落ち着いた声だった。引っかかりが言葉を得た感じがした、と私には見えた。

「名前だけ知っていても、仕組みが分からなければ使えない」とシェフは言った。「Facade が何をするのか、実演しよう」

注文は一言で十分だ

シェフはホールのカウンターを一度見てから、厨房の入り口の受け渡し口を指した。「ここに、注文口をひとつ作る。ホール係はここに伝票を一枚渡すだけ。中で何をどの順番でやるかは、厨房が管理する。ホール係は知らなくていい」

それがFacadeだ。複数のサブシステムを束ね、シンプルな窓口メソッドだけを外に公開する構造パターン。

実装は Kitchen.pm という新しいクラスを一枚作ることから始まる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package Kitchen;
use Moo;
use v5.36;
use Kitchen::Prep;
use Kitchen::Heat;
use Kitchen::Plate;

has _prep  => (is => 'lazy', default => sub { Kitchen::Prep->new });
has _heat  => (is => 'lazy', default => sub { Kitchen::Heat->new });
has _plate => (is => 'lazy', default => sub { Kitchen::Plate->new });

sub place_order {
    my ($self, $item) = @_;
    my $prepped = $self->_prep->prepare($item);
    my $cooked  = $self->_heat->cook($prepped);
    return $self->_plate->serve($cooked);
}

KitchenKitchen::Prep, Kitchen::Heat, Kitchen::Platehas で保持し、place_order の中で段取りをまとめる。外に公開するのは place_order だけだ。

そして DeliveryOrder はこうなる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package DeliveryOrder;
use Moo;
use v5.36;
use Kitchen;

has _kitchen => (is => 'ro', default => sub { Kitchen->new });

sub _log_received    { }
sub _dispatch_driver { }

sub execute {
    my ($self, $item) = @_;
    $self->_log_received($item);
    my $dish = $self->_kitchen->place_order($item);
    $self->_dispatch_driver($dish);
    return $dish;
}

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 を作って確かめてみると、その通りだった。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package Kitchen::Garnish;
use Moo;
use v5.36;
sub garnish { my ($self, $cooked) = @_; "$cooked(ガーニッシュ付き)" }

# ガーニッシュ対応版の Kitchen(中身だけが変わる)
package KitchenWithGarnish;
use Moo;
use v5.36;

has _prep    => (is => 'lazy', default => sub { Kitchen::Prep->new });
has _heat    => (is => 'lazy', default => sub { Kitchen::Heat->new });
has _garnish => (is => 'lazy', default => sub { Kitchen::Garnish->new });   # ← 追加
has _plate   => (is => 'lazy', default => sub { Kitchen::Plate->new });

sub place_order {
    my ($self, $item) = @_;
    my $prepped   = $self->_prep->prepare($item);
    my $cooked    = $self->_heat->cook($prepped);
    my $garnished = $self->_garnish->garnish($cooked);                      # ← 追加
    return $self->_plate->serve($garnished);
}

DeliveryOrder のコードは一行も変えずに、_kitchenKitchenWithGarnish を渡すだけで動く。

1
2
3
my $order = DeliveryOrder->new(_kitchen => KitchenWithGarnish->new);
$order->execute('カレー');
# → 'カレー(仕込み済み)(加熱済み)(ガーニッシュ付き)(盛り付け済み)'

DeliveryOrderKitchenWithGarnish という名前すら知らない。知っているのは place_order というメソッド名だけだ。

私は配膳カウンターの端をもう一度見た。伝票を一枚渡すだけのホール係と、受け取って中で全部やる厨房の受け渡し口。DeliveryOrderKitchen の関係は、そういうことだと思った。

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(DeliveryOrderKitchen だけを知る):

	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

試食合格

テストを走らせた。

1
2
3
4
5
6
ok 1 - 通常の注文: Before と同じ結果
ok 2 - DeliveryOrder が知っているのは Kitchen だけ
ok 3 - DeliveryOrder に _prep は存在しない
ok 4 - ガーニッシュ追加: DeliveryOrder は変えずに動く
ok 5 - DeliveryOrder に _garnish は追加されていない
...

全テスト通過、警告なし。

彼はAfterのコードを眺めた。DeliveryOrder.pm の先頭は use Kitchen の一行になっている。

use Kitchen::Prep が、消えた」と彼は言った。

「ガーニッシュがまた変わっても、DeliveryOrder を開かなくていい」とシェフが言った。

「Kitchen だけ変える」

「そうだ」

彼は少し間を置いた。「来てよかった」と言った。大げさでなく、ただ事実を確かめた口調だった。


シェフの仕込み工程表

問題(調理ミス)技法(パターン)効果(仕上がり)
注文ハンドラが厨房の複数クラスを直接 use し、段取りの順番まで自分で管理している(caller-knows-subsystem)Facadeパターン:窓口クラスを一枚作り、複数サブシステムへの委譲と段取りをまとめる厨房側の変更が DeliveryOrder に波及しなくなる。呼び出し元は place_order だけを知ればよい

工程

  1. 「呼び出し元のファイルに use SomeSubsystem が複数行並んでいるか」を確認する
  2. 呼び出し元がサブシステムを呼ぶ順番(段取り)をベタ書きしているコードを探す
  3. その「段取り」を引き受けるFacadeクラス(今回は Kitchen.pm)を作り、has でサブシステムを保持し、段取りをメソッドにまとめる
  4. 呼び出し元の use をFacadeクラスの一行に置き換え、段取り部分をFacadeメソッドの呼び出し一行に変える
  5. テストを実行し、Before と After で処理結果が変わらないことを確認する
  6. 「サブシステム側に変更が起きたとき、呼び出し元を触らなくて済むか」を確認する

シェフより

「ホール係に厨房の仕込み方を全部覚えさせるな。厨房が変わるたびにホール係が覚え直すのは、仕事の分け方がおかしい。窓口を一つ作れば、中が変わっても外は変えなくていい。垣根はそのためにある」


彼を見送ってドアが閉まると、厨房からシェフが戻ってきた。私は片付けを続けながら、今日のことをもう一度なぞろうとした。

先に気づいた。合っていた。なぜかは、今もうまく言葉にできない。use Kitchen::Prep use Kitchen::Heat use Kitchen::Plate——注文を受けるクラスに、厨房の部署が並んでいた。それを見たとき、何かが落ち着かなかった。厨房の段取りを、注文ハンドラが知っていることへの違和感。

なぜおかしいのかは、シェフに説明してもらうまで言葉にできなかった。でも「おかしい」という感じは、先にあった。

誰かのコードを見て、先に「これ、おかしい気がします」と言えた。今日が初めてだった。

少し、こわいことだと思った。コードの匂いを嗅ぎ始めている、料理の人間が。

でも手応えがある。次の注文が来るのを、少しだけ待てる気がした。

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。