@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制約を使ってバグを防ぐ方法を学びます。お楽しみに!