前回の振り返り
前回は、複数のAPIを統合しようとしてコードが破綻していく様子を体験しました。if文が肥大化し、同じ処理が重複し、新しいAPIの追加が困難になりました。
今回は、この問題を「Adapterパターン」で解決します。
今回の目標
第3回となる今回は、Adapterパターンを使って各APIのインターフェースを統一します。どのAPIを使っても同じ get_weather($city) というメソッドで天気情報を取得できるようにします。
Adapterパターンとは
Adapterパターンは、互換性のないインターフェースを変換して、一緒に動作できるようにするデザインパターンです。
日常生活の例で言えば、海外旅行で使う電源アダプターと同じです。日本のプラグ(インターフェースA)を、海外のコンセント(インターフェースB)に接続できるように変換します。
APIへの適用
今回のケースでは、各APIの異なるレスポンス構造を、共通の形式に変換します。
| |
Adapterパターンのクラス図
classDiagram
class WeatherAdapter_Role {
<<interface>>
+get_weather(city)*
+name*
+show_weather(city)
}
class WeatherAdapter_OpenWeatherMap {
+name
-_get_raw_data(city)
+get_weather(city)
}
class WeatherAdapter_WeatherStack {
+name
-_get_raw_data(city)
+get_weather(city)
}
WeatherAdapter_OpenWeatherMap ..|> WeatherAdapter_Role : implements
WeatherAdapter_WeatherStack ..|> WeatherAdapter_Role : implements
各Adapterは共通のRole(インターフェース)を実装することで、呼び出し側からは同じ方法で扱えるようになります。
共通インターフェースの設計
まず、すべてのAdapterが持つべき共通インターフェースを定義します。Moo::Roleを使って実装します。
| |
このRoleは以下を定義しています。
requires 'get_weather'- 各Adapterが必ず実装すべきメソッドrequires 'name'- サービス名を返すメソッド(後の回で使用)show_weather- 共通の表示処理
OpenWeatherMap用Adapterの実装
まず、OpenWeatherMap API用のAdapterを作成します。
| |
ポイントはget_weatherメソッドです。OpenWeatherMap固有のデータ構造を、共通形式のハッシュリファレンスに変換しています。
WeatherStack用Adapterの実装
次に、WeatherStack API用のAdapterを作成します。
| |
WeatherStackの固有データ構造(location.name、current.temperatureなど)を、同じ共通形式に変換しています。
完成コード
すべてをまとめた完成コードです。
| |
実行結果:
| |
Adapterパターンのメリット
ポリモーフィズムの実現
どのAdapterも同じget_weatherメソッドを持っているため、呼び出し側はどのAPIを使っているか意識する必要がありません。
| |
単一責任の原則(SRP)への準拠
各Adapterは「自分のAPIのデータを共通形式に変換する」という単一の責任だけを持っています。
新しいAPIの追加が容易
3つ目のAPIを追加する場合、既存のコードを修正する必要はありません。新しいAdapterクラスを追加するだけです。
| |
これは開放閉鎖の原則(OCP)に準拠しています。
まとめ
今回は、Adapterパターンを使って複数のAPIを統一的に扱えるようにしました。
- Moo::Roleで共通インターフェースを定義した
- 各API用のAdapterクラスを作成した
- ポリモーフィズムにより、どのAdapterも同じように扱えるようになった
- 単一責任の原則、開放閉鎖の原則に準拠した設計になった
コードがだいぶスッキリしました。
次回予告
Adapterでインターフェースは統一できましたが、まだ問題が残っています。呼び出し側で「どのAdapterを使うか」「失敗したら次を試す」などのロジックが必要です。次回は、この問題を解決するために「Facadeパターン」を導入します。お楽しみに!
