はじめに
前回は、WeatherService と OldWeatherAPI という2つの天気サービスを扱おうとして、インターフェースの違いによる問題を体験しました。
今回は、この問題を解決するための「橋渡しクラス」を作成します。OldWeatherAPI を WeatherService と同じインターフェースで使えるようにしましょう。
前回の振り返り
前回発生した問題を整理します。
| 問題 | 詳細 |
|---|
| メソッド名の違い | get_weather vs fetch_weather_info |
| 戻り値形式の違い | ハッシュリファレンス vs 文字列 |
| 統一処理の困難さ | ループで同じように扱えない |
今回の目標
第3回となる今回は、OldWeatherAPI を WeatherService と同じインターフェースで使えるようにする「橋渡しクラス」OldWeatherAdapter を作成します。
新しい概念: 委譲とラッピング
今回学ぶ新しい概念は「委譲とラッピング」です。
- 委譲(delegation): 処理を別のオブジェクトに任せること
- ラッピング: 既存のオブジェクトを包み込み、別のインターフェースを提供すること
橋渡しクラスは、内部で OldWeatherAPI のインスタンスを保持し(委譲先)、外部には WeatherService と同じインターフェースを提供します(ラッピング)。
橋渡しクラスの設計
橋渡しクラス OldWeatherAdapter は、以下の役割を持ちます。
- 内部で
OldWeatherAPI のインスタンスを保持する get_weather メソッドを実装し、内部で fetch_weather_info を呼び出す- 戻り値を文字列からハッシュリファレンスに変換する
show_weather メソッドを実装する
OldWeatherAdapterクラスの定義
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
| package OldWeatherAdapter {
use v5.36;
use Moo;
# OldWeatherAPIのインスタンスを保持(委譲先)
has 'old_api' => (
is => 'ro',
required => 1,
);
# WeatherServiceと同じインターフェースを提供
sub get_weather ($self, $city) {
# OldWeatherAPIのメソッドを呼び出し
my $info = $self->old_api->fetch_weather_info($city);
# 戻り値を変換: '晴れ/25度' -> { condition => '晴れ', temperature => 25 }
if ($info eq '情報なし') {
return { condition => '不明', temperature => 0 };
}
my ($condition, $temp_str) = split '/', $info;
$temp_str =~ s/度$//; # '25度' -> '25'
return {
condition => $condition,
temperature => int($temp_str),
};
}
sub show_weather ($self, $city) {
my $weather = $self->get_weather($city);
say "$city の天気: $weather->{condition}(気温: $weather->{temperature}℃)";
}
}
|
このクラスのポイントを解説します。
ポイント1: 委譲先の保持
1
2
3
4
| has 'old_api' => (
is => 'ro',
required => 1,
);
|
has を使って OldWeatherAPI のインスタンスを保持します。これが「委譲」の基盤となります。コンストラクタで OldWeatherAPI のインスタンスを受け取ります。
ポイント2: インターフェースの変換
1
2
3
4
| sub get_weather ($self, $city) {
my $info = $self->old_api->fetch_weather_info($city);
# ... 変換処理 ...
}
|
外部からは get_weather として呼び出されますが、内部では fetch_weather_info を呼び出しています。これがインターフェースの「橋渡し」です。
ポイント3: 戻り値の変換
'晴れ/25度' という文字列を { condition => '晴れ', temperature => 25 } というハッシュリファレンスに変換しています。これにより、呼び出し元は WeatherService と同じ形式でデータを受け取れます。
橋渡しクラスを使った統一的な呼び出し
橋渡しクラスを使うことで、2つのサービスを同じように扱えるようになります。
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
| #!/usr/bin/env perl
use v5.36;
# WeatherService クラス(第1回と同じ)
package WeatherService {
use v5.36;
use Moo;
sub get_weather ($self, $city) {
my %weather_data = (
'東京' => { condition => '晴れ', temperature => 25 },
'大阪' => { condition => '曇り', temperature => 23 },
'札幌' => { condition => '雨', temperature => 18 },
);
return $weather_data{$city} // { condition => '不明', temperature => 0 };
}
sub show_weather ($self, $city) {
my $weather = $self->get_weather($city);
say "$city の天気: $weather->{condition}(気温: $weather->{temperature}℃)";
}
}
# OldWeatherAPI クラス(第2回と同じ)
package OldWeatherAPI {
use v5.36;
use Moo;
sub fetch_weather_info ($self, $location) {
my %data = (
'東京' => '晴れ/25度',
'大阪' => '曇り/23度',
'名古屋' => '晴れ/26度',
);
return $data{$location} // '情報なし';
}
}
# OldWeatherAdapter クラス(橋渡しクラス)
package OldWeatherAdapter {
use v5.36;
use Moo;
has 'old_api' => (
is => 'ro',
required => 1,
);
sub get_weather ($self, $city) {
my $info = $self->old_api->fetch_weather_info($city);
if ($info eq '情報なし') {
return { condition => '不明', temperature => 0 };
}
my ($condition, $temp_str) = split '/', $info;
$temp_str =~ s/度$//;
return {
condition => $condition,
temperature => int($temp_str),
};
}
sub show_weather ($self, $city) {
my $weather = $self->get_weather($city);
say "$city の天気: $weather->{condition}(気温: $weather->{temperature}℃)";
}
}
# メイン処理
package main {
use v5.36;
# 新サービス
my $new_service = WeatherService->new;
# 旧APIを橋渡しクラスでラップ
my $old_api = OldWeatherAPI->new;
my $adapted_old = OldWeatherAdapter->new(old_api => $old_api);
say "=== 統一インターフェースでの呼び出し ===";
say "";
# どちらも同じメソッドで呼び出せる!
say "【新サービス】";
$new_service->show_weather('東京');
$new_service->show_weather('大阪');
say "";
say "【旧API(橋渡しクラス経由)】";
$adapted_old->show_weather('東京');
$adapted_old->show_weather('名古屋');
}
|
実行結果:
1
2
3
4
5
6
7
8
9
| === 統一インターフェースでの呼び出し ===
【新サービス】
東京 の天気: 晴れ(気温: 25℃)
大阪 の天気: 曇り(気温: 23℃)
【旧API(橋渡しクラス経由)】
東京 の天気: 晴れ(気温: 25℃)
名古屋 の天気: 晴れ(気温: 26℃)
|
橋渡しクラスの効果
橋渡しクラスを導入したことで、以下の効果が得られました。
| 問題 | 解決策 |
|---|
| メソッド名の違い | 橋渡しクラスが get_weather を提供 |
| 戻り値形式の違い | 橋渡しクラスが変換処理を実行 |
| 統一処理の困難さ | 同じメソッドで呼び出せるようになった |
重要なポイント
橋渡しクラスは、既存のクラス(OldWeatherAPI)を変更せずに、新しいインターフェースを提供しています。これは非常に重要な特徴です。
- レガシーコードに手を加える必要がない
- 既存の動作を壊すリスクがない
- 新旧のシステムを共存させられる
今回の完成コード
今回作成した完成コードを以下に示します。
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
| #!/usr/bin/env perl
use v5.36;
# WeatherService クラス
# 新しい天気情報サービス(統一インターフェースの基準となる)
package WeatherService {
use v5.36;
use Moo;
sub get_weather ($self, $city) {
my %weather_data = (
'東京' => { condition => '晴れ', temperature => 25 },
'大阪' => { condition => '曇り', temperature => 23 },
'札幌' => { condition => '雨', temperature => 18 },
);
return $weather_data{$city} // { condition => '不明', temperature => 0 };
}
sub show_weather ($self, $city) {
my $weather = $self->get_weather($city);
say "$city の天気: $weather->{condition}(気温: $weather->{temperature}℃)";
}
}
# OldWeatherAPI クラス
# レガシーな天気情報API(インターフェースが異なる)
package OldWeatherAPI {
use v5.36;
use Moo;
sub fetch_weather_info ($self, $location) {
my %data = (
'東京' => '晴れ/25度',
'大阪' => '曇り/23度',
'名古屋' => '晴れ/26度',
);
return $data{$location} // '情報なし';
}
}
# OldWeatherAdapter クラス
# OldWeatherAPIをWeatherServiceと同じインターフェースで使えるようにする橋渡しクラス
package OldWeatherAdapter {
use v5.36;
use Moo;
# 委譲先: OldWeatherAPIのインスタンス
has 'old_api' => (
is => 'ro',
required => 1,
);
# WeatherServiceと同じインターフェース
sub get_weather ($self, $city) {
# OldWeatherAPIのメソッドを呼び出し(委譲)
my $info = $self->old_api->fetch_weather_info($city);
# 戻り値の変換
if ($info eq '情報なし') {
return { condition => '不明', temperature => 0 };
}
my ($condition, $temp_str) = split '/', $info;
$temp_str =~ s/度$//;
return {
condition => $condition,
temperature => int($temp_str),
};
}
sub show_weather ($self, $city) {
my $weather = $self->get_weather($city);
say "$city の天気: $weather->{condition}(気温: $weather->{temperature}℃)";
}
}
# メイン処理
package main {
use v5.36;
say "=== 天気情報ツール(橋渡しクラス版)===";
say "";
# 新サービス(そのまま使用)
my $new_service = WeatherService->new;
# 旧APIを橋渡しクラスでラップ
my $old_api = OldWeatherAPI->new;
my $adapted_old = OldWeatherAdapter->new(old_api => $old_api);
say "【新サービス】";
$new_service->show_weather('東京');
$new_service->show_weather('大阪');
$new_service->show_weather('札幌');
say "";
say "【旧API(橋渡しクラス経由)】";
$adapted_old->show_weather('東京');
$adapted_old->show_weather('大阪');
$adapted_old->show_weather('名古屋');
say "";
say "--- 統一インターフェースの効果 ---";
say "・どちらも show_weather メソッドで呼び出せる";
say "・どちらも同じ形式の戻り値を返す";
say "・既存のOldWeatherAPIは変更していない";
}
|
実行方法:
1
| perl weather_adapter.pl
|
まとめ
今回は、インターフェースが異なる OldWeatherAPI を WeatherService と同じ形式で使えるようにする橋渡しクラス OldWeatherAdapter を作成しました。
- 委譲を使って既存のオブジェクトを内部に保持した
- ラッピングによって新しいインターフェースを提供した
- 既存のコードを変更せずに、統一的な呼び出しが可能になった
次回予告
次回は、3つ目のサービスを追加して、複数のサービスをループで処理する方法を学びます。統一インターフェースの真価が発揮されるシーンを体験しましょう!