Featured image of post 第4回-共通の約束を決める - PerlとMooで航空管制シミュレーターを作ろう

第4回-共通の約束を決める - PerlとMooで航空管制シミュレーターを作ろう

全航空機クラスに共通のインターフェースをMoo::Roleで定義。Aircraft::Roleを作成し、統一的な操作を実現します。

Roleでインターフェースを定義

前回の振り返り

前回は、管制塔クラスを導入して航空機間の相互依存を解消しました。

今回は、航空機クラスに共通のインターフェースを定義してさらに設計を改善します。

なぜインターフェースが必要か

現在のAircraftクラスは1種類ですが、実際の空港には様々な航空機がやってきます。ジャンボジェットから小型のプロペラ機まで、サイズも見た目も様々です。

  • 旅客機(PassengerAircraft)— お客さんを乗せて飛ぶ
  • 貨物機(CargoAircraft)— 荷物専門の働き者
  • 小型機(SmallAircraft)— 身軽で小回りが利く

管制塔は、これらすべての航空機と同じ方法で通信できなければなりません。「あなたは旅客機だからこう話して、貨物機はこう…」なんて面倒ですよね。共通のインターフェースがあれば、どんな航空機でも同じ方法で扱えます。

Aircraft::Roleを定義

Moo::Roleを使って、航空機の共通インターフェースを定義します。Moo::Roleについては、以下のシリーズで詳しく解説しています。

1
2
3
4
5
6
7
8
package Aircraft::Role {
    use Moo::Role;

    requires 'request_landing';
    requires 'receive_clearance';

    has tower => (is => 'rw');
}

requiresで必須メソッドを宣言し、hasで共通の属性を定義しています。

旅客機クラス

Aircraft::Roleを使って旅客機クラスを作ります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package PassengerAircraft {
    use Moo;
    with 'Aircraft::Role';

    has flight_number => (is => 'ro', required => 1);
    has passengers => (is => 'ro', default => 0);

    sub request_landing($self) {
        say $self->flight_number . 
            "(旅客機): 着陸許可をリクエストします";
        $self->tower->request_landing($self);
    }

    sub receive_clearance($self, $cleared) {
        if ($cleared) {
            say $self->flight_number . ": 着陸許可を受信。着陸します";
            $self->tower->notify_landed($self);
        } else {
            say $self->flight_number . ": 待機指示を受信。待機します";
        }
    }
}

貨物機クラス

同様に貨物機クラスを作ります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package CargoAircraft {
    use Moo;
    with 'Aircraft::Role';

    has flight_number => (is => 'ro', required => 1);
    has cargo_weight => (is => 'ro', default => 0);

    sub request_landing($self) {
        say $self->flight_number . 
            "(貨物機): 着陸許可をリクエストします";
        $self->tower->request_landing($self);
    }

    sub receive_clearance($self, $cleared) {
        if ($cleared) {
            say $self->flight_number . ": 着陸許可を受信。着陸します";
            $self->tower->notify_landed($self);
        } else {
            say $self->flight_number . ": 待機指示を受信。待機します";
        }
    }
}

管制塔を修正

管制塔からreceive_clearanceを呼び出すように修正します。

 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 ControlTower {
    use Moo;

    has aircrafts => (is => 'ro', default => sub { [] });
    has runway_in_use => (is => 'rw', default => 0);

    sub register($self, $aircraft) {
        push @{$self->aircrafts}, $aircraft;
        $aircraft->tower($self);
        say "管制塔: " . $aircraft->flight_number . "を登録しました";
    }

    sub request_landing($self, $aircraft) {
        if ($self->runway_in_use) {
            say "管制塔: " . $aircraft->flight_number . 
                "、滑走路使用中です";
            $aircraft->receive_clearance(0);
            return;
        }
        $self->runway_in_use(1);
        say "管制塔: " . $aircraft->flight_number . 
            "、着陸を許可します";
        $aircraft->receive_clearance(1);
    }

    sub notify_landed($self, $aircraft) {
        $self->runway_in_use(0);
        say "管制塔: " . $aircraft->flight_number . 
            "の着陸を確認。滑走路クリア";
    }
}

完成コード

  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
#!/usr/bin/env perl
use v5.36;

package Aircraft::Role {
    use Moo::Role;

    requires 'request_landing';
    requires 'receive_clearance';

    has tower => (is => 'rw');
}

package ControlTower {
    use Moo;

    has aircrafts => (is => 'ro', default => sub { [] });
    has runway_in_use => (is => 'rw', default => 0);

    sub register($self, $aircraft) {
        push @{$self->aircrafts}, $aircraft;
        $aircraft->tower($self);
        say "管制塔: " . $aircraft->flight_number . "を登録しました";
    }

    sub request_landing($self, $aircraft) {
        if ($self->runway_in_use) {
            say "管制塔: " . $aircraft->flight_number . 
                "、滑走路使用中です";
            $aircraft->receive_clearance(0);
            return;
        }
        $self->runway_in_use(1);
        say "管制塔: " . $aircraft->flight_number . 
            "、着陸を許可します";
        $aircraft->receive_clearance(1);
    }

    sub notify_landed($self, $aircraft) {
        $self->runway_in_use(0);
        say "管制塔: " . $aircraft->flight_number . 
            "の着陸を確認。滑走路クリア";
    }
}

package PassengerAircraft {
    use Moo;
    with 'Aircraft::Role';

    has flight_number => (is => 'ro', required => 1);
    has passengers => (is => 'ro', default => 0);

    sub request_landing($self) {
        say $self->flight_number . 
            "(旅客機): 着陸許可をリクエストします";
        $self->tower->request_landing($self);
    }

    sub receive_clearance($self, $cleared) {
        if ($cleared) {
            say $self->flight_number . ": 着陸許可を受信。着陸します";
            $self->tower->notify_landed($self);
        } else {
            say $self->flight_number . ": 待機指示を受信。待機します";
        }
    }
}

package CargoAircraft {
    use Moo;
    with 'Aircraft::Role';

    has flight_number => (is => 'ro', required => 1);
    has cargo_weight => (is => 'ro', default => 0);

    sub request_landing($self) {
        say $self->flight_number . 
            "(貨物機): 着陸許可をリクエストします";
        $self->tower->request_landing($self);
    }

    sub receive_clearance($self, $cleared) {
        if ($cleared) {
            say $self->flight_number . ": 着陸許可を受信。着陸します";
            $self->tower->notify_landed($self);
        } else {
            say $self->flight_number . ": 待機指示を受信。待機します";
        }
    }
}

# 管制塔を作成
my $tower = ControlTower->new;

# 旅客機と貨物機を作成
my $passenger = PassengerAircraft->new(
    flight_number => 'JAL123',
    passengers => 180
);
my $cargo = CargoAircraft->new(
    flight_number => 'FDX456',
    cargo_weight => 50000
);

$tower->register($passenger);
$tower->register($cargo);

say "---";

$passenger->request_landing;
say "---";
$cargo->request_landing;

実行結果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
管制塔: JAL123を登録しました
管制塔: FDX456を登録しました
---
JAL123(旅客機): 着陸許可をリクエストします
管制塔: JAL123、着陸を許可します
JAL123: 着陸許可を受信。着陸します
管制塔: JAL123の着陸を確認。滑走路クリア
---
FDX456(貨物機): 着陸許可をリクエストします
管制塔: FDX456、着陸を許可します
FDX456: 着陸許可を受信。着陸します
管制塔: FDX456の着陸を確認。滑走路クリア

異なる種類の航空機が同じインターフェースで管制塔と通信できています。

Roleの効果

	classDiagram
    class AircraftRole {
        <<role>>
        +tower
        +flight_number()*
        +request_landing()*
        +receive_clearance()*
    }
    class PassengerAircraft {
        +passengers
    }
    class CargoAircraft {
        +cargo_weight
    }
    AircraftRole <|.. PassengerAircraft
    AircraftRole <|.. CargoAircraft
  • 管制塔はどの種類の航空機でも同じ方法で扱える
  • 新しい航空機の種類を追加しても、管制塔のコードを変更する必要がない
  • インターフェースを守れば、自由に新しい航空機クラスを作れる

今回のまとめ

今回は、Aircraft::Roleで航空機の共通インターフェースを定義しました。

  • requiresで必須メソッドを宣言
  • 異なる種類の航空機を統一的に扱える
  • 新しい航空機クラスの追加が容易

次回は、滑走路を別のクラスとして分離し、リソース管理の仕組みを実装します。

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