Featured image of post 状態オブジェクトを付け替える仕事を、VendingMachineクラスに引き取らせる【Context】

状態オブジェクトを付け替える仕事を、VendingMachineクラスに引き取らせる【Context】

これまで呼び出し側が $state = $state->insert_coin と状態オブジェクトを付け替えていたのを、VendingMachine クラス(Context)にまとめます。VendingMachine が現在の状態と在庫を属性に持ち、操作を状態オブジェクトに委譲して次の状態を受け取るので、使う側は状態クラスを知らなくて済みます。払い出し中の DispensingState もここで加わります。

Featured image of post 状態クラスがdispenseを書き忘れたら、withの時点で止めたい【Moo::Role requires】

状態クラスがdispenseを書き忘れたら、withの時点で止めたい【Moo::Role requires】

すべての状態クラスに insert_coin・select_product・dispense の3つを必ず持たせるため、Moo::Role の requires で VendingMachineState ロールを作り、各クラスに with で適用します。dispense を書き忘れた BrokenState が、呼び出すより前の with の時点でエラーになることと、does でロールを持っているか確かめる方法を見ます。

Featured image of post 状態をif/elseで見分けるのをやめて、「待機中」そのものをMooのクラスにする【状態クラス】

状態をif/elseで見分けるのをやめて、「待機中」そのものをMooのクラスにする【状態クラス】

if/elsif で状態を見分けるのをやめ、WaitingState と CoinInsertedState という状態ごとのクラスを Moo で作ります。各メソッドは自分の状態での振る舞いだけを書き、次の状態のオブジェクトを返す。if/else は消えますが、すべての状態クラスに同じメソッドを持たせる仕組みはまだありません。

Featured image of post 「払い出し中」と「売り切れ」を足したら、自動販売機のどのメソッドにも4つの分岐が並んだ【コードの臭い】

「払い出し中」と「売り切れ」を足したら、自動販売機のどのメソッドにも4つの分岐が並んだ【コードの臭い】

第1回の自動販売機に「払い出し中」と「売り切れ」の状態と dispense メソッドを足すと、insert_coin・select_product・dispense のそれぞれに4状態分の if/elsif が並び、在庫のチェックも入れ子になる。状態を1つ足すたびに全部のメソッドを直すことになるこの形を、コードの臭いとして整理します。

Featured image of post 自動販売機の「待機中」と「コイン投入済み」を、Perlのstate属性とif/elseで切り替える【if/else】

自動販売機の「待機中」と「コイン投入済み」を、Perlのstate属性とif/elseで切り替える【if/else】

Perl と Moo で自動販売機を作りながら、if/else の状態管理を State パターンへ組み直すまでを全10回でたどります。第1回は「待機中」と「コイン投入済み」の2つの状態を state 属性の文字列で持ち、insert_coin と select_product の中を if/elsif で分けて、同じボタンでも状態によって結果が変わる自動販売機を書きます。

Featured image of post if文がネストしていくPerlのユーザー登録バリデーションを、検証ルールごとのクラスに分けてChain of Responsibilityにたどり着く全3回【目次】

if文がネストしていくPerlのユーザー登録バリデーションを、検証ルールごとのクラスに分けてChain of Responsibilityにたどり着く全3回【目次】

Perl でユーザー登録フォームの入力検証を if 文で書き始め、パスワードや年齢のルールを足してネストが深くなったところで、Moo で検証ルールを1つずつクラスに分け、set_next でつなぐ Chain of Responsibility パターンに組み直す全3回の連載の目次です。どこから読むか、困りごとからも選べます。

Featured image of post ネストしたif文に埋もれた検証ルールを、1ルール1クラスに分けて鎖でつなぐ【Chain of Responsibility】

ネストしたif文に埋もれた検証ルールを、1ルール1クラスに分けて鎖でつなぐ【Chain of Responsibility】

ネストした validate_user を、Moo の Validator を継承する RequiredValidator や EmailValidator など、1つの検証ルールだけを受け持つクラスに分けます。set_next で順につなぎ、先頭の validate を呼ぶと入力がすべてのバリデータを通ってエラーが1つのハッシュに集まる、Chain of Responsibility パターンの形にします。禁止ワードのチェックは、クラスを1つ作ってチェーンに1つつなげば加えられます。

Featured image of post パスワード・確認一致・利用規約・年齢を足したら、validate_userのif文が3段にネストした【コードの臭い】

パスワード・確認一致・利用規約・年齢を足したら、validate_userのif文が3段にネストした【コードの臭い】

パスワードの必須と強度、確認用パスワードの一致、利用規約への同意、18歳以上という年齢制限を、第1回の validate_user に if/elsif で足していく。動きはするものの、確認用パスワードのチェックはパスワード強度の else の中にしか書けず、ネストは3段になる。1つの関数に全部の検証が集まったこの状態を、コードの臭いとして5つの問題に整理します。

Featured image of post ユーザー登録フォームの入力チェックを、Perlのif文だけで書いてみる。名前とメールアドレスから始める【if文】

ユーザー登録フォームの入力チェックを、Perlのif文だけで書いてみる。名前とメールアドレスから始める【if文】

ユーザー登録フォームの入力検証(バリデーション)を、Perl で全3回かけて組み直します。第1回は名前とメールアドレスの必須チェックと簡易な形式チェックを if 文と早期リターンで書き、最初のエラーで止まる版から、エラーをフィールド名のハッシュに集めてまとめて返す版へ直します。Moo のクラスはまだ出てきません。

Featured image of post 形式を足すたびにif/elseが伸びるPerlのデータエクスポーターを、Mooで育ててStrategyパターンにたどり着く全10回【目次】

形式を足すたびにif/elseが伸びるPerlのデータエクスポーターを、Mooで育ててStrategyパターンにたどり着く全10回【目次】

Perl と Moo でアドレス帳を CSV・JSON・YAML に書き出すデータエクスポーターを作りながら、形式を足すたびに伸びる if/else を形式ごとのクラスとロールに分け、実行中に差し替えられるようにし、最後に Strategy パターンだと名前を付ける全10回の連載の目次です。どこから読むか、困りごとからも選べます。

Featured image of post 形式ごとのクラスを差し替えて使う設計には、Strategyという名前がある。作ったクラスを3つの役割に当てはめる【Strategyパターン】

形式ごとのクラスを差し替えて使う設計には、Strategyという名前がある。作ったクラスを3つの役割に当てはめる【Strategyパターン】

9回かけて作ったデータエクスポーターは、GoF の Strategy パターンの実装だった。ExporterRole、CsvExporter などのエクスポーター、DataExporter を Strategy・ConcreteStrategy・Context に当てはめ、XML を足すときに既存のエクスポーターへ手を入れずに済むことを SOLID 原則とつなげて、最初の if/else 版と比べます。

Featured image of post 部品はそろったのに、まだYAMLで出せない。YamlExporterを足し、全部を1本のエクスポーターにまとめる【統合】

部品はそろったのに、まだYAMLで出せない。YamlExporterを足し、全部を1本のエクスポーターにまとめる【統合】

ExporterRole、CsvExporter と JsonExporter、DataExporter、exporter_for がそろった。YamlExporter を作って %exporter_map に yaml を足し、対応形式を返す supported_formats と --help を加えた完成版を1本のスクリプトにまとめ、csv・json・yaml の3形式で実行して出力を確かめます。第1回の if/else とも並べて比べます。

Featured image of post 出力はクラスに分けたのに、どのクラスをnewするかのif/elseがメインに残っている。形式名から選ぶexporter_forを作る【Factory的な選択】

出力はクラスに分けたのに、どのクラスをnewするかのif/elseがメインに残っている。形式名から選ぶexporter_forを作る【Factory的な選択】

形式ごとの出力はクラスに分けたが、メイン処理には形式名を見てどのエクスポーターを new するか選ぶ if/else がまだ残っている。形式名とクラス名を対応させたハッシュ %exporter_map を DataExporter に持たせ、形式名から new して返すクラスメソッド exporter_for を作って、メイン処理から if/else を消します。

Featured image of post エクスポーターでない値を入れても、export_dataを呼ぶまでエラーにならない。isaでExporterRoleかを確かめ、入れた時点で止める【isa】

エクスポーターでない値を入れても、export_dataを呼ぶまでエラーにならない。isaでExporterRoleかを確かめ、入れた時点で止める【isa】

exporter を rw にしたので、エクスポーターではない文字列も入れられてしまい、エラーは export_data で export を呼んだときにやっと出る。exporter 属性に、ExporterRole を持つかを does で確かめる isa を付け、new のときも差し替えのときも、間違った値を入れた時点で isa check failed のエラーで止まるのを確かめます。

Featured image of post 同じデータをCSVとJSONの両方で出したいのに、作ったあとでエクスポーターを替えられない。exporterをrwにして実行中に切り替える【実行時の切り替え】

同じデータをCSVとJSONの両方で出したいのに、作ったあとでエクスポーターを替えられない。exporterをrwにして実行中に切り替える【実行時の切り替え】

DataExporter の exporter 属性は読み取り専用(ro)なので、作ったあとでエクスポーターを替えられない。rw にして exporter(JsonExporter->new) で差し替え、同じ DataExporter で CSV と JSON を続けて出力します。代わりに、エクスポーターではない文字列まで入ってしまう問題が残ります。