@nqounetです。
前回は、URLパターンでハンドラーを振り分けるルーター機能を作りました。
今回は、これまで作ってきた機能を統合し、完成したディスパッチャーをBBSに組み込みます。
振り返り:ここまでの道のり
第1回から第10回まで、少しずつ機能を追加してきました。
- if/elseで機能を切り替える(問題発見)
- ハッシュで振り分ける(ディスパッチテーブル)
- 処理を変数に入れる(コードリファレンス)
- 処理をクラスに分ける(ハンドラークラス)
- 共通の約束を決める(requires)
- 司令塔クラスを作る(Dispatcher)
- 動的に切り替える
- ハンドラーを登録する(レジストリ)
- 自動で選ぶ仕組み(Factory)
- URLで振り分ける(ルーティング)
これらを組み合わせて、完成形を作りましょう。
完成したディスパッチャー
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
32
33
34
35
36
37
38
39
40
41
42
43
44
| package BBS::Dispatcher {
use Moo;
use Module::Load qw(load);
has routes => (
is => 'ro',
default => sub { [] },
);
has handler_namespace => (
is => 'ro',
default => sub { 'BBS::Handler' },
);
sub add_route {
my ($self, $pattern, $handler_name) = @_;
push @{$self->routes}, {
pattern => $pattern,
handler => $handler_name,
};
}
sub dispatch {
my ($self, $path) = @_;
# ルートをマッチング
for my $route (@{$self->routes}) {
if ($path =~ /^$route->{pattern}$/) {
my $handler = $self->_create_handler($route->{handler});
return $handler->run;
}
}
die "Not Found: $path";
}
sub _create_handler {
my ($self, $name) = @_;
my $class = $self->handler_namespace . '::' . $name;
eval { load $class };
die "Handler not found: $class" if $@;
return $class->new;
}
};
|
ルーティング、レジストリ、ファクトリーの機能を1つのクラスに統合しました。
BBSで使ってみよう
1
2
3
4
5
6
7
8
9
10
11
12
13
| # ディスパッチャーを作成
my $dispatcher = BBS::Dispatcher->new;
# ルートを登録
$dispatcher->add_route('/posts', 'List');
$dispatcher->add_route('/posts/new', 'Form');
$dispatcher->add_route('/posts/(\d+)', 'Detail');
$dispatcher->add_route('/threads', 'ThreadList');
# リクエストに応じてディスパッチ
$dispatcher->dispatch('/posts'); # BBS::Handler::List が実行される
$dispatcher->dispatch('/posts/new'); # BBS::Handler::Form が実行される
$dispatcher->dispatch('/posts/42'); # BBS::Handler::Detail が実行される
|
URLを渡すだけで、適切なハンドラーが選ばれて実行されます。
flowchart TD
subgraph BBS["BBS アプリケーション"]
D[Dispatcher]
R["routes[]"]
subgraph Handlers["ハンドラー"]
L[List]
F[Form]
DT[Detail]
T[ThreadList]
end
end
U[URLリクエスト] --> D
D --> R
R -->|マッチング| L
R -->|マッチング| F
R -->|マッチング| DT
R -->|マッチング| T
if/elseとの比較
第1回で書いた、if/elseで機能を切り替えるコードを思い出してください。
Before(if/else):
1
2
3
4
5
6
7
8
9
10
11
| if ($action eq 'list') {
show_list();
} elsif ($action eq 'form') {
show_form();
} elsif ($action eq 'thread') {
show_thread();
} elsif ($action eq 'detail') {
show_detail();
} else {
show_error();
}
|
After(ディスパッチャー):
1
| $dispatcher->dispatch($path);
|
コードがすっきりしただけでなく、新しい機能を追加するときもadd_routeでルートを追加するだけになりました。
まとめ
- ルーティング、レジストリ、ファクトリーを統合してディスパッチャーを完成させた
- URLを渡すだけで適切なハンドラーが選ばれて実行される
- if/elseの羅列から、シンプルで拡張しやすい設計になった
- 新しい機能を追加するときは
add_routeでルートを追加するだけ
次回予告
次回は最終回です。実は、私たちが作ってきたこの仕組みには名前があります。「デザインパターン」の世界を覗いてみましょう。お楽しみに。