サブクラスが create_report をオーバーライドすると、基底クラスの generate_and_print から呼ばれる処理がどう切り替わるかを追います。DailyReportGenerator を足しても既存のクラスを1行も直さずに済むことを確認します。
ReportGenerator を基底クラスにして、MonthlyReportGenerator と WeeklyReportGenerator を extends で継承させます。create_report の if/else がなくなり、使うレポートの種類はジェネレーターを選ぶだけで決まる形になります。
ReportRole を作り、requires で generate と get_period の実装を必須にします。with で適用したクラスがメソッドを実装し忘れると、どんなエラーでロールの適用に失敗するかを実際に動かして確認します。
週次レポートを追加するために、create_report に type 引数を渡して if/else で月次と週次を切り替えます。動きはしますが、日次や四半期を足すたびに ReportGenerator を書き換えることになり、開放閉鎖原則に反する形になる理由を確かめます。
月次レポートを表す MonthlyReport と、それを作って返す ReportGenerator を、Perl v5.36 と Moo で書きます。レポートを作る処理を create_report メソッドに閉じ込めておく、全10回の連載の出発点です。
if文で書いた決済審査が、条件を足すたびにネストして保守できなくなり、審査ごとのMooクラスを鎖でつなぐChain of Responsibilityにたどり着くまでの全3回の目次です。第1回から順に読む連載で、どの回で何が変わるかを一覧できます。
金額上限・有効期限・ブラックリストの審査を、PaymentChecker を継承した別々の Moo クラスに分け、next_handler でつないで鎖にします。審査の順序は組み立てるときに決まり、残高確認の BalanceChecker を足すのに既存のチェッカーは書き換えません。最後に Chain of Responsibility パターンと名前を付けます。
決済審査にブラックリスト、利用枠の残高確認、10分以内に3回以上の決済を拒否する不正利用検知を足すと、if-else のネストが5段になります。早期リターンで平らに書き直しても、1つの関数に5つの責任が残り、審査条件を1つだけ試すテストも書きにくいままです。この形を整理します。
架空ECサイト「ペルマート」の決済審査を、Perl v5.36 の関数1つで書き始めます。10万円以上の決済を拒否する金額上限チェックを書き、カードの有効期限チェックを足して、拒否条件に当たった時点で return する早期リターンでネストを増やさない形にします。
Perl で自動販売機の状態を if/else で管理するところから始め、状態が増えて分岐が膨らんだところで、状態ごとのクラスと Moo::Role、状態を抱える VendingMachine(Context)に組み直し、最後に State パターンだと名前を付ける全10回の連載の目次です。どこから読むか、困りごとからも選べます。
第9回までに作った VendingMachineState ロール・4つの状態クラス・VendingMachine を、GoF の State パターンの State・ConcreteState・Context に当てはめます。同じく振る舞いを差し替える Strategy パターンとは、切り替えを外のクライアントが決めるか状態自身が決めるか、状態遷移があるかで分かれることを、データエクスポーターのコードと並べて確かめます。
WaitingState・CoinInsertedState・DispensingState・SoldOutState の4つの状態クラスと VendingMachine を1本のスクリプトにまとめ、標準入力から c(コイン投入)・s(商品選択)・d(払い出し)・q(終了)を受け付ける対話的な CLI を付けて完成させます。状態遷移図とクラスの一覧で、第1回からの設計を振り返ります。
在庫が0になったら移る売り切れ状態 SoldOutState を追加します。新しいクラスを1つ作り、DispensingState の dispense に遷移先を1か所足すだけで、VendingMachine(Context)やロール、ほかの状態クラスは変えずに済む。この足し方を、開放閉鎖原則(OCP)の形として確かめます。
VendingMachine の state には文字列でも入ってしまい、エラーになるのは次に insert_coin を呼んだときになる。Moo の has に isa のコードリファレンスを書き、値が VendingMachineState ロールを持たなければ die するようにして、set_state を呼んだ時点で間違いに気づけるようにします。
次の状態を返すだけの状態クラスでは、在庫を見て遷移先を変えられない。各メソッドの引数に Context(VendingMachine)を渡し、状態クラスが $context->stock で在庫を確かめ、$context->set_state で自分から次の状態に切り替える形にします。在庫2個で3回買い、3回目は売り切れでコインが返されるのを確かめます。