Featured image of post 第2回-異なるAPIを持つサービスを追加する方法 - 天気情報ツールで覚えるPerl

第2回-異なるAPIを持つサービスを追加する方法 - 天気情報ツールで覚えるPerl

別の天気サービスを追加したいがメソッド名や戻り値が違う!異なるインターフェースの問題を体験。Perl/Moo第2回。

はじめに

前回は、シンプルな WeatherService クラスを作成しました。

今回は、別の天気情報サービスを追加してみます。しかし、この新しいサービスは WeatherService とは異なるメソッド名や戻り値の形式を持っています。2つのサービスを同時に使おうとすると、どのような問題が発生するのか体験していきましょう。

前回の振り返り

前回作成した WeatherService クラスは、以下の特徴を持っていました。

  • get_weather($city) メソッドで天気情報を取得
  • 戻り値は { condition => '晴れ', temperature => 25 } のようなハッシュリファレンス
  • show_weather($city) メソッドで整形表示

今回の目標

第2回となる今回は、既存のレガシーな天気API OldWeatherAPI を追加します。このクラスは WeatherService とは異なるインターフェースを持っており、2つを同時に扱おうとすると問題が発生することを体験します。

新しい概念: 異なるインターフェース

今回学ぶ新しい概念は「異なるインターフェース」です。同じ目的(天気情報の取得)を持つクラスでも、メソッド名や戻り値の形式が異なる場合があります。

OldWeatherAPIクラスの登場

開発を進めていると、既存システムで使用されている古い天気APIクラスを発見しました。このクラスも天気情報を提供しますが、インターフェースが異なります。

OldWeatherAPIクラスの定義

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package OldWeatherAPI {
    use v5.36;
    use Moo;

    # メソッド名が異なる: get_weather ではなく fetch_weather_info
    sub fetch_weather_info ($self, $location) {
        my %data = (
            '東京' => '晴れ/25度',
            '大阪' => '曇り/23度',
            '名古屋' => '晴れ/26度',
        );

        return $data{$location} // '情報なし';
    }
}

このクラスの特徴を見ていきましょう。

  • メソッド名が異なる: get_weather ではなく fetch_weather_info
  • 引数名が異なる: $city ではなく $location
  • 戻り値の形式が異なる: ハッシュリファレンスではなく、'晴れ/25度' のような文字列

2つのサービスを使ってみる

では、WeatherServiceOldWeatherAPI の両方を使って天気情報を表示しようとしてみましょう。

 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
#!/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 クラス(新しく追加)
package OldWeatherAPI {
    use v5.36;
    use Moo;

    sub fetch_weather_info ($self, $location) {
        my %data = (
            '東京' => '晴れ/25度',
            '大阪' => '曇り/23度',
            '名古屋' => '晴れ/26度',
        );

        return $data{$location} // '情報なし';
    }
}

# メイン処理
package main {
    use v5.36;

    my $new_service = WeatherService->new;
    my $old_api = OldWeatherAPI->new;

    say "=== 新しいサービス ===";
    $new_service->show_weather('東京');
    $new_service->show_weather('大阪');

    say "";
    say "=== 古いAPI ===";
    # 問題1: メソッド名が違う
    my $info = $old_api->fetch_weather_info('東京');
    say "東京 の天気: $info";

    $info = $old_api->fetch_weather_info('名古屋');
    say "名古屋 の天気: $info";
}

実行結果:

1
2
3
4
5
6
7
=== 新しいサービス ===
東京 の天気: 晴れ(気温: 25℃)
大阪 の天気: 曇り(気温: 23℃)

=== 古いAPI ===
東京 の天気: 晴れ/25度
名古屋 の天気: 晴れ/26度

問題点の整理

上記のコードを見ると、2つのサービスを扱う際に以下の問題があることがわかります。

問題1: メソッド名が異なる

サービスメソッド名
WeatherServiceget_weather
OldWeatherAPIfetch_weather_info

同じ「天気情報を取得する」という目的なのに、呼び出し方が異なります。

問題2: 戻り値の形式が異なる

サービス戻り値の形式
WeatherServiceハッシュリファレンス { condition => '晴れ', temperature => 25 }
OldWeatherAPI文字列 '晴れ/25度'

戻り値の扱い方も全く異なるため、呼び出し元のコードも変える必要があります。

問題3: 統一的な処理ができない

もし複数のサービスをループで処理したい場合、以下のような困ったコードになってしまいます。

1
2
3
4
5
6
7
8
# これは動きません!
my @services = ($new_service, $old_api);

for my $service (@services) {
    # どちらのメソッドを呼べばいい?
    # 戻り値の形式をどう扱えばいい?
    $service->show_weather('東京');  # OldWeatherAPI には show_weather がない!
}

なぜこの問題が起きるのか

この問題は、2つのクラスが異なるインターフェースを持っているために発生します。

  • インターフェース = クラスが外部に公開するメソッドの名前、引数、戻り値の形式

同じ目的を持つクラスでも、開発時期や開発者が異なると、インターフェースが一致しないことはよくあります。特にレガシーシステムと新システムを統合する際に頻繁に遭遇する問題です。

今回の完成コード

今回作成した、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
#!/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;

    # メソッド名: fetch_weather_info(get_weatherではない)
    # 引数名: $location($cityではない)
    # 戻り値: 文字列(ハッシュリファレンスではない)
    sub fetch_weather_info ($self, $location) {
        my %data = (
            '東京' => '晴れ/25度',
            '大阪' => '曇り/23度',
            '名古屋' => '晴れ/26度',
        );

        return $data{$location} // '情報なし';
    }
}

# メイン処理
package main {
    use v5.36;

    my $new_service = WeatherService->new;
    my $old_api = OldWeatherAPI->new;

    say "=== 天気情報ツール ===";
    say "";

    say "【新サービス(WeatherService)】";
    $new_service->show_weather('東京');
    $new_service->show_weather('大阪');
    $new_service->show_weather('札幌');

    say "";
    say "【旧API(OldWeatherAPI)】";
    say "東京 の天気: " . $old_api->fetch_weather_info('東京');
    say "大阪 の天気: " . $old_api->fetch_weather_info('大阪');
    say "名古屋 の天気: " . $old_api->fetch_weather_info('名古屋');

    say "";
    say "--- 問題点 ---";
    say "・メソッド名が異なる(get_weather vs fetch_weather_info)";
    say "・戻り値の形式が異なる(ハッシュリファレンス vs 文字列)";
    say "・統一的なループ処理ができない";
}

実行方法:

1
perl weather_service_problem.pl

まとめ

今回は、異なるインターフェースを持つ2つの天気サービスを扱う際の問題を体験しました。

  • OldWeatherAPIWeatherService とは異なるメソッド名・戻り値を持つ
  • 異なるインターフェースを持つクラスは、統一的に扱うことが難しい
  • 呼び出し元のコードが複雑になり、保守性が低下する

次回予告

次回は、この問題を解決するための「橋渡しクラス」を作成します。OldWeatherAPIWeatherService と同じインターフェースで使えるようにする方法を学びましょう!

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