Featured image of post 第6回-実行時に出力形式を切り替えよう - Mooを使ってデータエクスポーターを作ってみよう

第6回-実行時に出力形式を切り替えよう - Mooを使ってデータエクスポーターを作ってみよう

実行時にCSV→JSON→YAMLと出力形式を切り替えたい。is => 'rw'を活用した動的なオブジェクト切り替えの実装方法を解説します。

@nqounetです。

前回は、エクスポーターを管理するDataExporterクラスを作りました。

今回は、プログラム実行中にエクスポーターを切り替えられるようにします。

現在の問題点

現在のDataExporterクラスでは、エクスポーターは作成時に設定したら変更できません。

1
2
3
4
has exporter => (
    is       => 'ro',  # 読み取り専用
    required => 1,
);

is => 'ro'は「読み取り専用(read only)」という意味で、一度設定したら変更できません。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
is => 'ro' の場合

                         ❌ 変更不可!
┌────────────────┐      ┌────────────────┐
│  DataExporter  │      │  JsonExporter  │
│  ┌──────────┐  │  ←   │                │
│  │CsvExporter│ │      └────────────────┘
│  └──────────┘  │
└────────────────┘

しかし、次のようなケースでは実行中に切り替えたくなります:

  • 同じデータをCSVとJSONの両方で出力したい
  • ユーザーの選択に応じて動的に形式を変えたい

is => ‘rw’で書き換え可能にする

is => 'rw'に変更すると、属性を後から書き換えられるようになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
is => 'rw' の場合

                         ✅ 変更可能!
┌────────────────┐      ┌────────────────┐
│  DataExporter  │      │  JsonExporter  │
│  ┌──────────┐  │  ←   │                │
│  │CsvExporter│ │      └────────────────┘
│  └──────────┘  │
└────────────────┘
        ▼ 切り替え後
┌────────────────┐
│  DataExporter  │
│  ┌───────────┐ │
│  │JsonExporter│ │
│  └───────────┘ │
└────────────────┘
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package DataExporter {
    use Moo;
    use v5.36;

    has exporter => (
        is       => 'rw',  # 読み書き可能に変更
        required => 1,
    );

    sub export_data ($self, $data) {
        return $self->exporter->export($data);
    }
}

これで、$data_exporter->exporter($new_exporter)のように、後からエクスポーターを変更できます。

動的切り替えを試す

実行中にエクスポーターを切り替えて、同じデータを複数の形式で出力してみましょう。

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

# ========================================
# ExporterRole - エクスポーターの約束
# ========================================
package ExporterRole {
    use Moo::Role;
    requires 'export';
}

# ========================================
# CsvExporterクラス
# ========================================
package CsvExporter {
    use Moo;
    use v5.36;
    with 'ExporterRole';

    sub export ($self, $data) {
        my $output = "name,email,phone\n";
        for my $contact (@$data) {
            $output .= "$contact->{name},$contact->{email},$contact->{phone}\n";
        }
        return $output;
    }
}

# ========================================
# JsonExporterクラス
# ========================================
package JsonExporter {
    use Moo;
    use v5.36;
    use JSON::PP;
    with 'ExporterRole';

    sub export ($self, $data) {
        return JSON::PP->new->pretty->encode($data);
    }
}

# ========================================
# DataExporterクラス(エクスポーター管理)
# ========================================
package DataExporter {
    use Moo;
    use v5.36;

    has exporter => (
        is       => 'rw',  # 読み書き可能
        required => 1,
    );

    sub export_data ($self, $data) {
        return $self->exporter->export($data);
    }
}

# ========================================
# メイン処理
# ========================================
package main;

# アドレス帳データ
my @contacts = (
    { name => '田中太郎', email => 'tanaka@example.com', phone => '090-1234-5678' },
    { name => '鈴木花子', email => 'suzuki@example.com', phone => '080-2345-6789' },
    { name => '佐藤次郎', email => 'sato@example.com',   phone => '070-3456-7890' },
);

# 最初はCSV形式でエクスポート
my $data_exporter = DataExporter->new(exporter => CsvExporter->new);
say "=== CSV形式 ===";
print $data_exporter->export_data(\@contacts);

# JSON形式に切り替えてエクスポート
$data_exporter->exporter(JsonExporter->new);
say "\n=== JSON形式 ===";
print $data_exporter->export_data(\@contacts);

実行結果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
=== CSV形式 ===
name,email,phone
田中太郎,tanaka@example.com,090-1234-5678
鈴木花子,suzuki@example.com,080-2345-6789
佐藤次郎,sato@example.com,070-3456-7890

=== JSON形式 ===
[
   {
      "email" : "tanaka@example.com",
      "name" : "田中太郎",
      "phone" : "090-1234-5678"
   },
   ...
]

同じ$data_exporterオブジェクトを使って、CSVとJSONの両方の形式で出力できました!

何が嬉しいのか?

1. 柔軟性が向上

実行時の状況に応じて、出力形式を自由に切り替えられます。

2. オブジェクトの再利用

同じDataExporterオブジェクトを使い回せるため、メモリ効率が良くなります。

3. 動的な振る舞いの変更

ユーザーの入力やプログラムの状態に応じて、振る舞いを動的に変えられます。

注意点

is => 'rw'にすると、意図しないタイミングでエクスポーターが変更される可能性もあります。

また、現在のコードでは「エクスポーター以外のオブジェクト」も設定できてしまいます。これは問題です。

1
2
3
# こんな間違いをしてしまうかも
$data_exporter->exporter("これはエクスポーターではない文字列");
$data_exporter->export_data(\@contacts);  # エラー!

次回は、この問題を防ぐための「型チェック」を追加します。

まとめ

  • is => 'rw'で属性を書き換え可能にしました
  • 実行中にエクスポーターを動的に切り替えられるようになりました
  • 同じオブジェクトで複数の形式を出力できるようになりました
  • ただし、間違ったオブジェクトを設定できてしまう問題があります

次回「第7回-does制約でバグを防ごう」では、does制約を使ってバグを防ぐ方法を学びます。お楽しみに!

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