Featured image of post 第5回-共通の約束を決めよう - Mooを使ってディスパッチャーを作ってみよう

第5回-共通の約束を決めよう - Mooを使ってディスパッチャーを作ってみよう

各ハンドラークラスに「必ずrunメソッドを持つ」という約束をMoo::Roleのrequiresで定義して、インターフェースを作りましょう。

@nqounetです。

前回は、処理を専用のハンドラークラスに切り出して、責任を分離する方法を学びました。ListHandlerFormHandlerが共通のrunメソッドを持つ設計でしたね。

今回は、「すべてのハンドラーはrunメソッドを持つ」という約束をrequiresで明示的に定義します。

問題:約束が守られているかわからない

新しくThreadHandlerを追加するとき、runではなくexecuteというメソッド名で作ってしまったらどうなるでしょうか?

1
2
3
4
5
6
7
8
package ThreadHandler {
    use Moo;

    sub execute {  # runじゃなくてexecute…
        my ($self) = @_;
        print "スレッドを表示\n";
    }
};

コードは動きますが、呼び出す側で$handler->run()とすると「メソッドがない」というエラーになります。実行時にしかミスに気づけないのは困りますね。

「すべてのハンドラーはrunメソッドを持つ」という共通ルールを、コードで強制できないでしょうか。

解決策:Moo::Roleのrequiresで約束を定義

Moo::Roleにはrequiresという機能があります。これは「このロールを適用するクラスは、指定したメソッドを必ず実装しなければならない」という約束です。

前シリーズの第10回でMoo::Rolewithを学びましたね。

前回は属性やメソッドを提供するロールでしたが、今回はメソッドを要求するロールを作ります。他のプログラミング言語では、このような仕組みを「インターフェース」と呼ぶことがあります。

	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ロールを作ります。

1
2
3
4
5
package Handler {
    use Moo::Role;

    requires 'run';
};

たった3行ですが、これで「runメソッドを持つこと」が約束として定義されました。このロールをwithで適用したクラスは、runメソッドを実装していなければプログラム起動時にエラーになります。

ハンドラークラスでのwith適用

作成したHandlerロールを各ハンドラークラスに適用します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package Handler {
    use Moo::Role;

    requires 'run';
};

package ListHandler {
    use Moo;
    with 'Handler';  # Handlerロールを適用

    sub run {
        my ($self) = @_;
        print "投稿一覧を表示\n";
    }
};

package FormHandler {
    use Moo;
    with 'Handler';  # 同じロールを適用

    sub run {
        my ($self) = @_;
        print "投稿フォームを表示\n";
    }
};

my $list = ListHandler->new();
$list->run();  # 投稿一覧を表示

my $form = FormHandler->new();
$form->run();  # 投稿フォームを表示

もしrunメソッドを実装し忘れたクラスにwith 'Handler'を書くと、プログラム起動時にエラーが発生します。

1
Can't apply Handler to BrokenHandler - missing run

実行時ではなく起動時にエラーになるので、早い段階でミスに気づけます。

まとめ

  • requiresで「このメソッドを必ず実装せよ」という約束を定義できる
  • 約束を守らないクラスはプログラム起動時にエラーになる
  • この仕組みは他の言語では「インターフェース」と呼ばれる
  • 新しいハンドラーを追加する際の実装漏れを防げる

次回予告

次回は、ハンドラーを保持して処理を振り分けるディスパッチャークラスを作ります。handlesを使った委譲も活用しながら、司令塔となるクラスを設計していきましょう。

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