@nqounetです。
前回は、処理を専用のハンドラークラスに切り出して、責任を分離する方法を学びました。ListHandlerやFormHandlerが共通のrunメソッドを持つ設計でしたね。
今回は、「すべてのハンドラーはrunメソッドを持つ」という約束をrequiresで明示的に定義します。
問題:約束が守られているかわからない
新しくThreadHandlerを追加するとき、runではなくexecuteというメソッド名で作ってしまったらどうなるでしょうか?
| |
コードは動きますが、呼び出す側で$handler->run()とすると「メソッドがない」というエラーになります。実行時にしかミスに気づけないのは困りますね。
「すべてのハンドラーはrunメソッドを持つ」という共通ルールを、コードで強制できないでしょうか。
解決策:Moo::Roleのrequiresで約束を定義
Moo::Roleにはrequiresという機能があります。これは「このロールを適用するクラスは、指定したメソッドを必ず実装しなければならない」という約束です。
前シリーズの第10回でMoo::Roleとwithを学びましたね。
前回は属性やメソッドを提供するロールでしたが、今回はメソッドを要求するロールを作ります。他のプログラミング言語では、このような仕組みを「インターフェース」と呼ぶことがあります。
flowchart TB
subgraph Role["Handler Role"]
R["requires 'run'"]
end
Role -->|with| LH[ListHandler]
Role -->|with| FH[FormHandler]
Role -->|with| TH[ThreadHandler]
LH --> LR["sub run { ... }"]
FH --> FR["sub run { ... }"]
TH --> TR["sub run { ... }"]
Handlerロールの作成
requires 'run'を使って、Handlerロールを作ります。
| |
たった3行ですが、これで「runメソッドを持つこと」が約束として定義されました。このロールをwithで適用したクラスは、runメソッドを実装していなければプログラム起動時にエラーになります。
ハンドラークラスでのwith適用
作成したHandlerロールを各ハンドラークラスに適用します。
| |
もしrunメソッドを実装し忘れたクラスにwith 'Handler'を書くと、プログラム起動時にエラーが発生します。
| |
実行時ではなく起動時にエラーになるので、早い段階でミスに気づけます。
まとめ
requiresで「このメソッドを必ず実装せよ」という約束を定義できる- 約束を守らないクラスはプログラム起動時にエラーになる
- この仕組みは他の言語では「インターフェース」と呼ばれる
- 新しいハンドラーを追加する際の実装漏れを防げる
次回予告
次回は、ハンドラーを保持して処理を振り分けるディスパッチャークラスを作ります。handlesを使った委譲も活用しながら、司令塔となるクラスを設計していきましょう。

