2. ファクトリーパターン (Factory)
オブジェクトの生成を工場に委任して結合度を下げるファクトリーパターンについて学びましょう。
ひとことで言うと
「どのオブジェクトを作るか」を選ぶ仕事を工場1か所にまとめ、オブジェクトが必要な場所は工場に頼むだけにするパターンです。
問題: あちこちに散らばった「何を作るか」
会員に通知を送るプログラムを作るとしましょう。会員によって通知の方式が違います。
- SMSで受け取る会員
- メールで受け取る会員
通知を送る場所はいくつもあります。登録した時も、注文が終わった時も送ります。まず思いつくのは、通知を送る場所ごとに方式を見て、合う通知オブジェクトを作る方法です。
【通知】を規定しよう:
〈送る〉がなければならない(【文字列】の『内容』)
【通知】に従う【SMS通知】を設計しよう:
〈送る〉を作ろう(【文字列】の『内容』):
枠「[SMS] {『内容』}」を出力しよう
【通知】に従う【メール通知】を設計しよう:
〈送る〉を作ろう(【文字列】の『内容』):
枠「[メール] {『内容』}」を出力しよう
(参考:通知を送る場所ごとに「どの通知を作るか」を選ぶコードがあります)
〈登録お祝い〉を作ろう(【文字列】の『方式』):
『通知』を【通知】の空っぽにしよう
『方式』によって分けよう:
「SMS」の場合:
『通知』を新しい【SMS通知】()にしよう
「メール」の場合:
『通知』を新しい【メール通知】()にしよう
『通知』の〈送る〉(「ご登録ありがとうございます!」)を実行しよう
〈注文完了〉を作ろう(【文字列】の『方式』):
『通知』を【通知】の空っぽにしよう
『方式』によって分けよう:
「SMS」の場合:
『通知』を新しい【SMS通知】()にしよう
「メール」の場合:
『通知』を新しい【メール通知】()にしよう
『通知』の〈送る〉(「ご注文が完了しました。」)を実行しよう
〈登録お祝い〉(「SMS」)を実行しよう
〈注文完了〉(「メール」)を実行しよう
実行結果
[SMS] ご登録ありがとうございます!
[メール] ご注文が完了しました。
ちゃんと動きます。でも、通知を送る場所が増えると痛くなります。
- 同じ選び方がコピーされます。
〈登録お祝い〉と〈注文完了〉の前半はまったく同じです。パスワード変更、発送開始…通知を送る場所が10か所なら、このコードも10セットです。 - 新しい方式を入れるには、すべての場所を直さなければなりません。「アプリ通知」ができたら10か所すべてに
「アプリ」の場合:を入れる必要があり、1か所でも忘れると、そこではアプリ通知が届きません。 - 送る場所が知りすぎています。
〈登録お祝い〉は「お祝いを送る」ことだけを知っていればいいのに、【SMS通知】、【メール通知】というクラスの名前と作り方まで全部知っています。 - 間違った方式が見当違いの場所で爆発します。
「SNS」のように打ち間違えると、どの場合にも合わずに『通知』が空っぽのまま残り、しばらく後で〈送る〉を呼んだ時にやっとエラーになります。エラーメッセージを見ただけでは、打ち間違いが原因だとは分かりにくいです。
【通知】を規定しよう:
〈送る〉がなければならない(【文字列】の『内容』)
【通知】に従う【SMS通知】を設計しよう:
〈送る〉を作ろう(【文字列】の『内容』):
枠「[SMS] {『内容』}」を出力しよう
〈登録お祝い〉を作ろう(【文字列】の『方式』):
『通知』を【通知】の空っぽにしよう
『方式』によって分けよう:
「SMS」の場合:
『通知』を新しい【SMS通知】()にしよう
『通知』の〈送る〉(「ご登録ありがとうございます!」)を実行しよう
〈登録お祝い〉(「SNS」)を実行しよう
実行すると「『空っぽ』型の値にはメンバーとしてアクセスできません」というエラーになります。原因は打ち間違いなのに、エラーはその次の行で見当違いの話をしています。
解決策: 作る仕事を工場に任せる
ファクトリーパターンは、「何を作るかを選ぶコード」を1か所に移します。
- 作られるオブジェクトが守る約束、つまり規定を作ります。ここではすでにある
【通知】です。 - 工場を作り、方式を受け取って合うオブジェクトを返すメソッドを1つ置きます。選ぶコードはここだけにあります。
- オブジェクトが必要な場所は、工場に「SMS通知を1つください」と頼むだけです。何が作られたかは知らなくても、約束(
【通知】)どおり〈送る〉ができることさえ分かっていれば大丈夫です。 - 知らない方式が来たら、工場がその場で分かりやすいエラーを出します。
たとえ: カフェのカウンター
カフェでお客さんは「アイスコーヒーください」と言うだけです。豆をどれだけ挽くか、氷をいつ入れるかは知らなくてかまいません。新しいメニューができても、お客さんの注文のしかたはそのままで、変わるのはカウンターの奥だけです。ないメニューを言えば、カウンターですぐに「そのメニューはありません」と教えてくれます。カウンターこそが「工場」です。
だれが何をするの?
| 役割 | この例では | やること |
|---|---|---|
| 製品の約束 | 【通知】(規定) |
作られるオブジェクトができなければならないことを決めます |
| 具体的な製品 | 【SMS通知】、【メール通知】 |
実際に仕事をします |
| 工場 | 【通知工場】の〈作る〉 |
方式を見て、合う製品を作って返します |
| 使う場所 | 〈登録お祝い〉、〈注文完了〉 |
工場に頼み、受け取った製品を約束どおりに使います |
カナデで作る
同じプログラムをファクトリーパターンで作り直してみましょう。工場の〈作る〉は、オブジェクトを作らなくても呼べるように『私たち』のを付けた静的メソッドにします。
【通知】を規定しよう:
〈送る〉がなければならない(【文字列】の『内容』)
【通知】に従う【SMS通知】を設計しよう:
〈送る〉を作ろう(【文字列】の『内容』):
枠「[SMS] {『内容』}」を出力しよう
【通知】に従う【メール通知】を設計しよう:
〈送る〉を作ろう(【文字列】の『内容』):
枠「[メール] {『内容』}」を出力しよう
(参考:どの通知を作るかは、この工場1か所だけで決めます)
【通知工場】を設計しよう:
【通知】を返す『私たち』の〈作る〉を作ろう(【文字列】の『方式』):
『方式』によって分けよう:
「SMS」の場合:
新しい【SMS通知】()を返そう
「メール」の場合:
新しい【メール通知】()を返そう
新しい【エラー】(枠「知らない通知方式です: {『方式』}」)を発生させよう
(参考:通知を送る場所は、工場に頼むだけです)
〈登録お祝い〉を作ろう(【文字列】の『方式』):
【通知工場】の〈作る〉(『方式』)の〈送る〉(「ご登録ありがとうございます!」)を実行しよう
〈注文完了〉を作ろう(【文字列】の『方式』):
【通知工場】の〈作る〉(『方式』)の〈送る〉(「ご注文が完了しました。」)を実行しよう
〈登録お祝い〉(「SMS」)を実行しよう
〈注文完了〉(「メール」)を実行しよう
とりあえずやってみよう:
〈注文完了〉(「SNS」)を実行しよう
発生したら(『エラー』):
(『エラー』の『メッセージ』)を出力しよう
実行結果
[SMS] ご登録ありがとうございます!
[メール] ご注文が完了しました。
知らない通知方式です: SNS
最初の2行はリファクタリング前と同じです。最後の行を見てください。今度は打ち間違いが入ってきたまさにその場所(工場)で、何がおかしいのかを教えてくれます。
1つずつもう一度
- 選ぶ場所は1か所。
によって分けようは【通知工場】の〈作る〉にしかありません。通知を送る場所が10か所に増えても、選ぶコードは1つです。 - 送る場所は1行。
〈登録お祝い〉は【SMS通知】や【メール通知】という名前をまったく知りません。工場から【通知】を1つ受け取って〈送る〉だけです。 - 返す型は約束で。
【通知】を返すと書いたので、工場が約束に従わないものを返すとカナデが教えてくれます。受け取る側は安心して〈送る〉を呼べます。 - 知らない方式は工場が止めます。 どの場合にも合わなければ、最後の行の
発生させようが実行されます。空っぽがプログラムの中に漏れ出しません。
新しい方式を足してみると
「アプリ通知」ができたとしましょう。
- 前のやり方: 通知を送るすべての場所を探して、
「アプリ」の場合:を入れなければなりません。 - ファクトリーパターン:
【通知】に従う【アプリ通知】を作り、【通知工場】に「アプリ」の場合:を1つだけ足せば済みます。〈登録お祝い〉、〈注文完了〉はそのままです。
やってみよう
上のコードに【アプリ通知】を足してみてください。〈送る〉は[アプリ] ご登録ありがとうございます!のように出力します。それから【通知工場】に「アプリ」を足して、〈登録お祝い〉(「アプリ」)を実行してみましょう。〈登録お祝い〉と〈注文完了〉は1文字も直さなくていいはずです。
もう一歩: ファクトリーメソッド
工場を別に置く代わりに、子クラスが何を作るかを決める方法もあります。親は「通知を1つ作って送る」という手順だけを決めておき、どの通知を作るかはなければならないで空けておきます。これをファクトリーメソッドと呼びます。親を下設計しよう(抽象クラス)で作ればできます。
【通知】を規定しよう:
〈送る〉がなければならない(【文字列】の『内容』)
【通知】に従う【SMS通知】を設計しよう:
〈送る〉を作ろう(【文字列】の『内容』):
枠「[SMS] {『内容』}」を出力しよう
【通知】に従う【メール通知】を設計しよう:
〈送る〉を作ろう(【文字列】の『内容』):
枠「[メール] {『内容』}」を出力しよう
(参考:手順は親が決め、どの通知を作るかは子が決めます)
【案内窓口】を下設計しよう:
〈通知を作る〉がなければならない()
〈案内する〉を作ろう(【文字列】の『内容』):
『通知』を【通知】の『私』の〈通知を作る〉()にしよう
「[案内開始]」を出力しよう
『通知』の〈送る〉(『内容』)を実行しよう
【案内窓口】をもとにして【SMS窓口】を設計しよう:
【通知】を返す〈通知を作る〉を作ろう():
新しい【SMS通知】()を返そう
【案内窓口】をもとにして【メール窓口】を設計しよう:
【通知】を返す〈通知を作る〉を作ろう():
新しい【メール通知】()を返そう
『窓口たち』を【新しい【SMS窓口】(), 新しい【メール窓口】()】にしよう
『窓口たち』の『窓口』ごとに繰り返そう:
『窓口』の〈案内する〉(「明日はお休みです。」)を実行しよう
実行結果
[案内開始]
[SMS] 明日はお休みです。
[案内開始]
[メール] 明日はお休みです。
〈案内する〉は親に1回だけあり、子は〈通知を作る〉を1つ埋めるだけです。新しい窓口ができたら、【案内窓口】をもとにしてクラスをもう1つ作ればいいんです。
どっちを使う?
何を作るかが実行中に入ってくる値(ユーザー設定、入力)で決まるなら、前の工場クラスが便利です。何を作るかがどんな種類のオブジェクトかで決まるなら、ファクトリーメソッドが自然です。
どんな時に使うといいの?
- 「何を作るか」を選ぶコードがあちこちにコピーされ始めた時
- オブジェクトを使う側が具体的なクラスの名前を知らなくて済むようにしたい時
- 新しい種類がよく増えそうな時
- 間違った種類を作る瞬間に止めたい時
いいところと気になるところ
| いいところ | 気になるところ |
|---|---|
| 選ぶコードが1か所にまとまって直しやすいです | クラス(工場)が1つ増えます |
| 使う側が具体的なクラスを知らなくて済みます | 作る場所が1か所だけなら、かえって遠回りです |
| 新しい種類を足しても、使う側はそのままです | 工場の中のによって分けようは、種類が増えるたびに長くなります |
| 間違った種類を、作る場所で止められます |
早すぎるパターンは毒です。
オブジェクトを作る場所が1か所だけで、種類も増える予定がないなら、その場で新しい【SMS通知】()と書くのがいちばん読みやすいです。同じ選び方を2回目にコピーしようとした瞬間が、工場を作る時です。
ほかのパターンとの関係
- ストラテジーパターンと相性がいいです。「VIPなら割合割引、クーポンがあればクーポン割引」のように、どの戦略を差し込むかを工場に決めさせれば、選ぶコードが1か所にまとまります。
- ビルダーパターンと比べてみましょう。 ファクトリーはどの種類を作るか選ぶことに集中し、ビルダーは1つのものをいくつもの段階で組み立てることに集中します。
- シングルトンパターンと一緒に使えます。 このページの工場のように
『私たち』の静的メソッドしかない工場はオブジェクトを作る必要すらありませんが、工場が設定などの値を覚えておく必要があるなら、1つだけ作ってみんなで使えばいいんです。