前回の振り返り
前回は、Adapterパターンを使って各APIのインターフェースを統一しました。どのAdapterも同じget_weatherメソッドで天気情報を取得できるようになりました。
しかし、まだ問題が残っています。今回は、その問題を明らかにします。
今回の目標
第4回となる今回は、Adapterパターンだけでは解決できない課題を整理します。なぜ「もう一つのパターン」が必要なのかを理解することが目標です。
呼び出し側のコードを見てみる
前回の完成コードでは、呼び出し側(メイン処理)で以下のことをしていました。
1
2
3
4
5
6
7
| # 各Adapterを作成
my $owm = WeatherAdapter::OpenWeatherMap->new;
my $ws = WeatherAdapter::WeatherStack->new;
# それぞれを個別に呼び出し
$owm->show_weather('Tokyo');
$ws->show_weather('Tokyo');
|
このコードには、以下の問題があります。
問題1: Adapterの管理が呼び出し側の責任
利用者は「どのAdapterがあるか」「どのように作成するか」を知っている必要があります。新しいAPIが追加されたら、呼び出し側のコードも更新が必要です。
問題2: フォールバック処理の実装が必要
APIは時々失敗します。「OpenWeatherMapが失敗したらWeatherStackを試す」というフォールバック処理を、呼び出し側で実装する必要があります。
1
2
3
4
5
6
7
8
| my $weather = $owm->get_weather('Tokyo');
if (!$weather) {
$weather = $ws->get_weather('Tokyo');
}
if (!$weather) {
$weather = $wa->get_weather('Tokyo');
}
# まだまだ続く...
|
問題3: 共通処理の重複
複数の場所でAdapterを使う場合、毎回同じような初期化処理とフォールバック処理を書くことになります。
実際の利用シーンを想像する
天気予報アグリゲーターを実際のアプリケーションで使う場面を想像してみましょう。
ケース1: Webアプリケーション
1
2
3
4
5
6
7
8
9
10
| # コントローラー
sub weather_action ($self, $city) {
my $owm = WeatherAdapter::OpenWeatherMap->new;
my $ws = WeatherAdapter::WeatherStack->new;
my $weather = $owm->get_weather($city);
$weather //= $ws->get_weather($city);
return { weather => $weather };
}
|
ケース2: CLIツール
1
2
3
4
5
6
7
8
| # CLIツール
my $owm = WeatherAdapter::OpenWeatherMap->new;
my $ws = WeatherAdapter::WeatherStack->new;
my $weather = $owm->get_weather($city);
$weather //= $ws->get_weather($city);
say format_weather($weather);
|
ケース3: バッチ処理
1
2
3
4
5
6
7
8
9
10
| # バッチ処理
for my $city (@cities) {
my $owm = WeatherAdapter::OpenWeatherMap->new;
my $ws = WeatherAdapter::WeatherStack->new;
my $weather = $owm->get_weather($city);
$weather //= $ws->get_weather($city);
save_to_database($weather);
}
|
同じようなコードが3箇所に散らばっています。これはDRY原則(Don’t Repeat Yourself)に違反しています。
理想的な呼び出し方
利用者が本当に欲しいのは、こんなシンプルなインターフェースです。
1
2
3
| # 理想的な呼び出し方
my $facade = WeatherFacade->new;
my $weather = $facade->get_weather('Tokyo');
|
利用者が気にすべきでないこと:
- どのAPIサービスを使っているか
- APIが何種類あるか
- どの順番で試すか
- 失敗時にどうフォールバックするか
これらはすべて「WeatherFacade」の内部で処理されるべきです。
Facadeパターンの予告
この「複雑な内部処理を隠蔽し、シンプルなインターフェースを提供する」パターンを「Facadeパターン」と呼びます。
Facadeは「建物の正面(ファサード)」を意味します。建物の裏側がどんなに複雑でも、正面から見れば美しい一枚の壁に見える。それと同じです。
Facadeの責務
WeatherFacadeは以下の責務を持ちます。
- 複数のAdapterを管理する
- 適切なAdapterを選択する
- 失敗時に次のAdapterを試す(フォールバック)
- 呼び出し側にはシンプルなインターフェースを提供する
クラス構成のイメージ
1
2
3
4
5
6
7
| 呼び出し側
↓ get_weather('Tokyo')
WeatherFacade
↓ 内部で管理
├── WeatherAdapter::OpenWeatherMap
├── WeatherAdapter::WeatherStack
└── WeatherAdapter::WeatherAPI
|
現状のコードの問題点まとめ
現状のAdapterパターンだけの実装には、以下の問題があります。
| 問題 | 説明 |
|---|
| Adapter管理の責任 | 呼び出し側がAdapterを知っている必要がある |
| フォールバック処理 | 呼び出し側で実装が必要 |
| コードの重複 | 複数箇所で同じ処理を書く |
| 変更の影響範囲 | API追加時に複数箇所の修正が必要 |
今回の完成コード
今回は新しいコードを書くのではなく、問題を整理しました。前回のコードに「こんな使い方をしたい」という呼び出し側のコードを追加したものを示します。
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
| #!/usr/bin/env perl
use v5.36;
# (前回のAdapter定義は省略)
# メイン処理:現状の問題点を示す
package main {
use v5.36;
say "=== 現状の問題点 ===";
say "";
# 問題1: Adapterの管理が呼び出し側の責任
my $owm = WeatherAdapter::OpenWeatherMap->new;
my $ws = WeatherAdapter::WeatherStack->new;
# 問題2: フォールバック処理が呼び出し側に必要
my $weather = $owm->get_weather('Tokyo');
if (!$weather) {
$weather = $ws->get_weather('Tokyo');
}
if ($weather) {
say "Tokyo: $weather->{condition}(気温 $weather->{temperature}℃)";
}
say "";
say "--- 理想的な呼び出し方 ---";
say '# my $facade = WeatherFacade->new;';
say '# my $weather = $facade->get_weather("Tokyo");';
say '# → これだけで済むようにしたい!';
}
|
まとめ
今回は、Adapterパターンだけでは解決できない課題を整理しました。
- 呼び出し側にAdapter管理の責任が残っている
- フォールバック処理を呼び出し側で実装する必要がある
- 同じコードが複数箇所に散らばる(DRY原則違反)
これらの問題を解決するために、次回は「Facadeパターン」を導入します。
次回予告
次回は、WeatherFacadeクラスを実装します。複数のAdapterを内部で管理し、フォールバック機能を備えた統一インターフェースを提供します。呼び出し側のコードがどれだけシンプルになるか、お楽しみに!