@nqounetです。
前回の振り返り
前回は、create_reportメソッドのオーバーライドについて詳しく解説しました。
- 基底クラスでメソッドの「枠組み」を定義する
- サブクラスでメソッドをオーバーライドして「具体的な処理」を実装する
- 新しい種別を追加しても既存コードを修正しなくてよい
今回の目標
今回は、基底クラスに共通の処理を集約します。
具体的には、以下のことを行います。
generate_and_saveメソッドを基底クラスに追加する- 「レポート生成→表示→保存」という一連の流れを統一する
- サブクラスは
create_reportだけに集中できるようにする
ストーリー設定
上司から「レポートを生成したら、自動的にファイルに保存してほしい」と依頼されました。
月次・週次・日次、すべてのレポートで同じ保存処理を行いたいとのこと。
各ジェネレーターに同じ保存処理を書くのは面倒なので、基底クラスにまとめてしまいましょう。
実装
コード例1: generate_and_saveメソッドの実装
基底クラスにgenerate_and_saveメソッドを追加します。
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
| #!/usr/bin/env perl
use v5.36;
# ========================================
# ReportRole ロール
# ========================================
package ReportRole {
use Moo::Role;
requires 'generate';
requires 'get_period';
}
# ========================================
# MonthlyReport クラス
# ========================================
package MonthlyReport {
use Moo;
with 'ReportRole';
has title => (
is => 'ro',
required => 1,
);
sub generate ($self) {
my @lines = (
"=== " . $self->title . " ===",
"期間: " . $self->get_period(),
"月次レポートを生成しました。",
);
return join("\n", @lines);
}
sub get_period ($self) {
return '月次';
}
}
# ========================================
# WeeklyReport クラス
# ========================================
package WeeklyReport {
use Moo;
with 'ReportRole';
has title => (
is => 'ro',
required => 1,
);
sub generate ($self) {
my @lines = (
"=== " . $self->title . " ===",
"期間: " . $self->get_period(),
"週次レポートを生成しました。",
);
return join("\n", @lines);
}
sub get_period ($self) {
return '週次';
}
}
# ========================================
# DailyReport クラス
# ========================================
package DailyReport {
use Moo;
with 'ReportRole';
has title => (
is => 'ro',
required => 1,
);
sub generate ($self) {
my @lines = (
"=== " . $self->title . " ===",
"期間: " . $self->get_period(),
"日次レポートを生成しました。",
);
return join("\n", @lines);
}
sub get_period ($self) {
return '日次';
}
}
# ========================================
# ReportGenerator 基底クラス
# ========================================
package ReportGenerator {
use Moo;
# サブクラスでオーバーライドするメソッド
sub create_report ($self, $title) {
die "create_report() must be implemented by subclass";
}
# 共通の処理: レポートを生成して表示
sub generate_and_print ($self, $title) {
my $report = $self->create_report($title);
my $content = $report->generate();
say $content;
return $report;
}
# 共通の処理: レポートを生成して保存
sub generate_and_save ($self, $title, $filename) {
# 1. レポートを生成
my $report = $self->create_report($title);
# 2. レポートの内容を取得
my $content = $report->generate();
# 3. 画面に表示
say $content;
# 4. ファイルに保存(シミュレーション)
say "";
say "[保存] $filename に保存しました。";
return $report;
}
}
# ========================================
# MonthlyReportGenerator クラス
# ========================================
package MonthlyReportGenerator {
use Moo;
extends 'ReportGenerator';
sub create_report ($self, $title) {
return MonthlyReport->new(title => $title);
}
}
# ========================================
# WeeklyReportGenerator クラス
# ========================================
package WeeklyReportGenerator {
use Moo;
extends 'ReportGenerator';
sub create_report ($self, $title) {
return WeeklyReport->new(title => $title);
}
}
# ========================================
# DailyReportGenerator クラス
# ========================================
package DailyReportGenerator {
use Moo;
extends 'ReportGenerator';
sub create_report ($self, $title) {
return DailyReport->new(title => $title);
}
}
# ========================================
# メイン処理
# ========================================
package main;
say "=== generate_and_save の使用例 ===";
say "";
my $monthly = MonthlyReportGenerator->new();
$monthly->generate_and_save(
"2026年1月 売上レポート",
"monthly_report_202601.txt"
);
say "";
my $weekly = WeeklyReportGenerator->new();
$weekly->generate_and_save(
"2026年1月第1週 売上レポート",
"weekly_report_202601_w1.txt"
);
|
ポイントは、generate_and_saveメソッドが基底クラスに定義されていることです。
create_reportを呼び出してレポートを生成(※サブクラスがオーバーライド)generateを呼び出してレポートの内容を取得- 画面に表示
- ファイルに保存
この流れは全てのジェネレーターで共通です。
コード例2: 実行結果の確認
実行結果は以下のようになります。
1
2
3
4
5
6
7
8
9
10
11
12
13
| === generate_and_save の使用例 ===
=== 2026年1月 売上レポート ===
期間: 月次
月次レポートを生成しました。
[保存] monthly_report_202601.txt に保存しました。
=== 2026年1月第1週 売上レポート ===
期間: 週次
週次レポートを生成しました。
[保存] weekly_report_202601_w1.txt に保存しました。
|
月次レポートも週次レポートも、同じgenerate_and_saveメソッドで処理されています。
基底クラスに共通処理を集約するメリット
この設計には以下のメリットがあります。
- 重複コードの排除
各ジェネレーターに同じ保存処理を書く必要がありません。
- 変更が容易
保存処理を変更したいとき、基底クラスの1箇所だけを修正すれば全てに反映されます。
- サブクラスの責務が明確
サブクラスはcreate_reportだけに集中できます。「何を作るか」だけを決めればよいのです。
処理の流れを図解
sequenceDiagram
participant Main as メイン処理
participant Generator as ReportGenerator<br/>(サブクラス)
participant Report as Report
Main->>Generator: generate_and_save(title, filename)
Note over Generator: 1. レポートを生成
Generator->>Generator: create_report(title)
Generator->>Report: new(title)
Report-->>Generator: report
Note over Generator: 2. 内容を取得
Generator->>Report: generate()
Report-->>Generator: content
Note over Generator: 3. 画面に表示
Generator->>Generator: say content
Note over Generator: 4. ファイルに保存
Generator->>Generator: ファイル書き込み
Generator-->>Main: report
基底クラスが「処理の骨格」を定義し、サブクラスが「具体的な部品」を提供する。この構造により、共通処理と個別処理を綺麗に分離できます。
今回のまとめ
今回は、基底クラスに共通処理を集約しました。
generate_and_saveメソッドを基底クラスに追加した- 「生成→表示→保存」の流れを統一した
- サブクラスは
create_reportだけに集中できるようになった
基底クラスで「処理の骨格」を定義し、サブクラスで「具体的な部品」を提供する。この設計パターンは、コードの再利用性を高め、保守性を向上させます。
次回予告
次回は「レポートの型を保証しよう」として、create_reportの戻り値が正しいレポートオブジェクトであることを保証します。
Mooのisaを使って、型安全性を高めましょう。