
前回の振り返り
前回は、緊急事態の航空機を優先する機能を追加しました。
今回はいよいよ最終回。このシリーズで作ってきたものの正体を明かします。
パターンの名前
このシリーズで作ってきた「管制塔が航空機間の通信を仲介する」という設計には、名前がついています。
GoF(Gang of Four)のデザインパターンの一つ、Mediatorパターン(仲介者パターン)です。
Mediatorパターンは、複数のオブジェクト間の複雑な相互作用を、仲介者オブジェクトにカプセル化するパターンです。
classDiagram
class Mediator {
<<interface>>
+notify(colleague, event)
}
class ConcreteMediator {
-colleagues: Colleague[]
+notify(colleague, event)
}
class Colleague {
<<interface>>
-mediator: Mediator
}
class ConcreteColleagueA
class ConcreteColleagueB
Mediator <|.. ConcreteMediator
Colleague <|.. ConcreteColleagueA
Colleague <|.. ConcreteColleagueB
ConcreteMediator --> ConcreteColleagueA
ConcreteMediator --> ConcreteColleagueB
Colleague --> Mediator
本シリーズでの対応:
| GoFの用語 | 本シリーズの実装 |
|---|
| Mediator | 管制塔のインターフェース |
| ConcreteMediator | ControlTowerクラス |
| Colleague | Aircraft::Role |
| ConcreteColleague | Aircraftクラス(各航空機) |
問題: N×(N-1)の相互依存
第2回で見たように、オブジェクト同士が直接やり取りすると、相互依存が爆発的に増加します。
graph LR
A[航空機A] <--> B[航空機B]
B <--> C[航空機C]
A <--> C
A <--> D[航空機D]
B <--> D
C <--> D
解決策: 仲介者を通じた通信
Mediatorパターンを使うと、すべての通信が仲介者を経由します。依存関係がシンプルになります。
graph TD
M[管制塔]
A[航空機A] <--> M
B[航空機B] <--> M
C[航空機C] <--> M
D[航空機D] <--> M
- 疎結合: 同僚オブジェクト同士は互いを知らない
- 一元管理: 相互作用のロジックが仲介者に集約される
- 拡張性: 新しい同僚オブジェクトを追加しても、既存のコードを変更しなくてよい
- 再利用性: 同僚オブジェクトを別の仲介者と組み合わせて再利用できる
他の応用例
Mediatorパターンは様々な場面で活用されています。
チャットルーム
複数のユーザーがチャットルーム(仲介者)を通じてメッセージをやり取りする。
graph TD
R[チャットルーム]
U1[ユーザーA] <--> R
U2[ユーザーB] <--> R
U3[ユーザーC] <--> R
GUIダイアログ
ダイアログ内の複数のUI要素(ボタン、テキストフィールド、チェックボックス)がダイアログ(仲介者)を通じて連携する。
スマートホーム
照明、エアコン、センサーなどのデバイスがスマートホームハブ(仲介者)を通じて協調動作する。
Observerパターンとの違い
同じGoFパターンのObserverパターンと混同しやすいので、違いを明確にしておきましょう。
| 項目 | Mediatorパターン | Observerパターン |
|---|
| 目的 | 相互作用を中央集権化 | 状態変化を通知 |
| 関係 | 多対多(双方向) | 一対多(単方向) |
| 通信の流れ | Colleague ↔ Mediator ↔ Colleague | Subject → Observer |
| 使い所 | 複雑な相互依存の整理 | 状態変化の通知・購読 |
本サイトでは、Observerパターンを使ったシリーズも公開しています。興味があれば読み比べてみてください。
シリーズ全体の振り返り
全8回を通じて、以下のことを学びました。
| 回 | 学んだこと |
|---|
| 第1回 | 航空機クラスの基本実装 |
| 第2回 | 直接参照による相互依存の問題 |
| 第3回 | 管制塔による仲介の導入 |
| 第4回 | Moo::Roleによるインターフェース定義 |
| 第5回 | 滑走路の排他制御(リソース管理) |
| 第6回 | 着陸待ちキュー(FIFO) |
| 第7回 | 緊急事態の優先処理 |
| 第8回 | Mediatorパターンの解説 |
最終コード
シリーズで作成した航空管制シミュレーターの最終版です。
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
| #!/usr/bin/env perl
use v5.36;
package Aircraft::Role {
use Moo::Role;
requires 'request_landing';
requires 'receive_clearance';
has tower => (is => 'rw');
}
package Runway {
use Moo;
has name => (is => 'ro', required => 1);
has occupied_by => (is => 'rw', default => undef);
sub is_available($self) {
return !defined $self->occupied_by;
}
sub occupy($self, $aircraft) {
$self->occupied_by($aircraft);
say "滑走路" . $self->name . ": " .
$aircraft->flight_number . "が使用開始";
}
sub release($self) {
my $aircraft = $self->occupied_by;
$self->occupied_by(undef);
say "滑走路" . $self->name . ": " .
$aircraft->flight_number . "が使用終了";
}
}
package ControlTower {
use Moo;
has aircrafts => (is => 'ro', default => sub { [] });
has runway => (is => 'ro', required => 1);
has waiting_queue => (is => 'ro', default => sub { [] });
sub register($self, $aircraft) {
push @{$self->aircrafts}, $aircraft;
$aircraft->tower($self);
say "管制塔: " . $aircraft->flight_number . "を登録";
}
sub request_landing($self, $aircraft) {
if (!$self->runway->is_available) {
if ($aircraft->is_emergency) {
say "管制塔: " . $aircraft->flight_number .
" [緊急] 優先キューに追加";
unshift @{$self->waiting_queue}, $aircraft;
} else {
say "管制塔: " . $aircraft->flight_number .
" キューに追加";
push @{$self->waiting_queue}, $aircraft;
}
$aircraft->receive_clearance(0);
return;
}
$self->_grant_landing($aircraft);
}
sub _grant_landing($self, $aircraft) {
$self->runway->occupy($aircraft);
my $msg = $aircraft->is_emergency ? " [緊急着陸許可]" : " 着陸許可";
say "管制塔: " . $aircraft->flight_number . $msg;
$aircraft->receive_clearance(1);
}
sub notify_landed($self, $aircraft) {
$self->runway->release;
$self->_process_queue;
}
sub _process_queue($self) {
return if @{$self->waiting_queue} == 0;
my $next = shift @{$self->waiting_queue};
$self->_grant_landing($next);
}
}
package Aircraft {
use Moo;
with 'Aircraft::Role';
has flight_number => (is => 'ro', required => 1);
has is_emergency => (is => 'rw', default => 0);
sub declare_emergency($self) {
$self->is_emergency(1);
say $self->flight_number . ": MAYDAY!";
}
sub request_landing($self) {
$self->tower->request_landing($self);
}
sub receive_clearance($self, $cleared) {
if ($cleared) {
say $self->flight_number . ": 着陸";
$self->tower->notify_landed($self);
}
}
}
# デモ
my $runway = Runway->new(name => 'A');
my $tower = ControlTower->new(runway => $runway);
my @flights = map { Aircraft->new(flight_number => $_) }
qw(JAL123 ANA456 SKY789);
$tower->register($_) for @flights;
say "---";
$_->request_landing for @flights;
|
まとめ
このシリーズでは、航空管制シミュレーターを題材に、Mediatorパターンを学びました。
- 複数オブジェクト間の相互作用を仲介者に集約
- オブジェクト間の結合度を下げ、保守性を向上
- 新しいオブジェクトの追加が容易
Mediatorパターンは、複雑な相互依存を整理したいときに非常に有効なパターンです。ぜひ自分のプロジェクトでも活用してみてください。
前提知識となる「Mooで覚えるオブジェクト指向プログラミング」シリーズも合わせてどうぞ。