@nqounetです。
前回の振り返り
前回は、レポートクラスに共通のルールを定義しました。
ReportRoleを作成し、requiresで必須メソッドを宣言したMonthlyReportとWeeklyReportにwithでロールを適用した- ロールを実装しないとエラーになることを確認した
しかし、まだif/elseの問題は解決していませんでした。
今回の目標
今回は、継承を使ってジェネレーターを種別ごとに分けます。
具体的には、以下のことを行います。
ReportGeneratorを基底クラスとして整理するMonthlyReportGeneratorクラスを作成するWeeklyReportGeneratorクラスを作成する
これにより、if/elseを使わずにレポートの種類を切り替えられるようになります。
classDiagram
class ReportGenerator {
+create_report(title)*
+generate_and_print(title)
}
class MonthlyReportGenerator {
+create_report(title)
}
class WeeklyReportGenerator {
+create_report(title)
}
ReportGenerator <|-- MonthlyReportGenerator : extends
ReportGenerator <|-- WeeklyReportGenerator : extends
MonthlyReportGenerator ..> MonthlyReport : creates
WeeklyReportGenerator ..> WeeklyReport : creates
この図は、ReportGenerator基底クラスを継承(extends)した2つのサブクラスが、それぞれ対応するレポートを生成する構造を示しています。
ストーリー設定
if/elseが増えてきて、コードが読みにくくなってきました。
「月次レポートの生成処理」と「週次レポートの生成処理」を分離して、それぞれ専用のジェネレーターを作ることにしましょう。
実装
コード例1: ReportGenerator基底クラスの整理
まず、ReportGeneratorを基底クラスとして整理します。
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
| #!/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) {
say "=== " . $self->title . " ===";
say "期間: " . $self->get_period();
say "月次レポートを生成しました。";
}
sub get_period ($self) {
return '月次';
}
}
# ========================================
# WeeklyReport クラス
# ========================================
package WeeklyReport {
use Moo;
with 'ReportRole';
has title => (
is => 'ro',
required => 1,
);
sub generate ($self) {
say "=== " . $self->title . " ===";
say "期間: " . $self->get_period();
say "週次レポートを生成しました。";
}
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);
$report->generate();
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);
}
}
# ========================================
# メイン処理
# ========================================
package main;
say "--- 月次レポート ---";
my $monthly_generator = MonthlyReportGenerator->new();
$monthly_generator->generate_and_print("2026年1月 売上レポート");
say "";
say "--- 週次レポート ---";
my $weekly_generator = WeeklyReportGenerator->new();
$weekly_generator->generate_and_print("2026年1月第1週 売上レポート");
|
大きな変更点は以下の通りです。
ReportGeneratorは基底クラスになり、create_reportメソッドは「サブクラスで実装すべき」というエラーを出すMonthlyReportGeneratorはextendsでReportGeneratorを継承し、create_reportをオーバーライドWeeklyReportGeneratorも同様に継承とオーバーライド
継承について詳しくは、前提知識のシリーズなどをご覧ください。
コード例2: 実行結果の確認
実行結果は前回と同じです。
1
2
3
4
5
6
7
8
9
| --- 月次レポート ---
=== 2026年1月 売上レポート ===
期間: 月次
月次レポートを生成しました。
--- 週次レポート ---
=== 2026年1月第1週 売上レポート ===
期間: 週次
週次レポートを生成しました。
|
重要なポイントは、if/elseがなくなったことです。
レポートの種類に応じて、適切なジェネレーターを使うだけです。
1
2
3
4
5
| # 月次レポートが欲しいとき
my $generator = MonthlyReportGenerator->new();
# 週次レポートが欲しいとき
my $generator = WeeklyReportGenerator->new();
|
継承による設計のメリット
この設計には以下のメリットがあります。
- if/elseが不要: 種別に応じたジェネレーターを選ぶだけ
- 責任の分離: 各ジェネレーターは自分が担当するレポートだけを知っている
- 拡張が容易: 新しいレポート種別を追加するときは、新しいジェネレーターを作るだけ
今回のまとめ
今回は、継承を使ってジェネレーターを種別ごとに分けました。
ReportGeneratorを基底クラスとして整理したMonthlyReportGeneratorを作成し、create_reportを実装したWeeklyReportGeneratorを作成し、create_reportを実装した- if/elseが不要になった
これで、if/elseの肥大化問題は解決しました。
しかし、まだcreate_reportメソッドの実装方法に改善の余地があります。次回は、このメソッドのオーバーライドについて詳しく見ていきます。
次回予告
次回は「生成処理をオーバーライドしよう」として、各ジェネレーターでcreate_reportをオーバーライドする仕組みを詳しく解説します。
さらにDailyReportGeneratorも追加して、拡張性を確認しましょう。