@nqounetです。
前回の振り返り
前回は、月次レポートを生成する基本的な仕組みを作りました。
MonthlyReportクラス: レポートの内容を表すクラスReportGeneratorクラス: レポートを生成するクラスcreate_reportメソッド: 具体的なレポートオブジェクトを生成する
今回の目標
今回は「週次レポートも生成したい」という新しい要望に対応します。
具体的には、以下のことを行います。
WeeklyReportクラスを新しく作成するcreate_reportメソッドに引数を追加し、レポートの種類を切り替える- if/elseで生成するレポートを切り替える
そして、この方法の問題点を確認します。
ストーリー設定
上司から「週次レポートも欲しい」と依頼されました。
月次レポートだけでなく、週ごとの売上推移も確認したいとのことです。
「簡単そうだな」と思いながら、コードを修正してみましょう。
実装
コード例1: if/elseでレポート種別を切り替えるコード
まず、WeeklyReportクラスを追加し、create_reportメソッドで種別を切り替えます。
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
| #!/usr/bin/env perl
use v5.36;
# ========================================
# MonthlyReport クラス
# ========================================
package MonthlyReport {
use Moo;
has title => (
is => 'ro',
required => 1,
);
has period => (
is => 'ro',
default => sub { '月次' },
);
sub generate ($self) {
say "=== " . $self->title . " ===";
say "期間: " . $self->period;
say "月次レポートを生成しました。";
}
}
# ========================================
# WeeklyReport クラス(新規追加)
# ========================================
package WeeklyReport {
use Moo;
has title => (
is => 'ro',
required => 1,
);
has period => (
is => 'ro',
default => sub { '週次' },
);
sub generate ($self) {
say "=== " . $self->title . " ===";
say "期間: " . $self->period;
say "週次レポートを生成しました。";
}
}
# ========================================
# ReportGenerator クラス
# ========================================
package ReportGenerator {
use Moo;
sub create_report ($self, $type, $title) {
if ($type eq 'monthly') {
return MonthlyReport->new(title => $title);
}
elsif ($type eq 'weekly') {
return WeeklyReport->new(title => $title);
}
else {
die "Unknown report type: $type";
}
}
sub generate_and_print ($self, $type, $title) {
my $report = $self->create_report($type, $title);
$report->generate();
return $report;
}
}
# ========================================
# メイン処理
# ========================================
package main;
my $generator = ReportGenerator->new();
say "--- 月次レポート ---";
$generator->generate_and_print('monthly', "2026年1月 売上レポート");
say "";
say "--- 週次レポート ---";
$generator->generate_and_print('weekly', "2026年1月第1週 売上レポート");
|
実行結果は以下のようになります。
1
2
3
4
5
6
7
8
9
| --- 月次レポート ---
=== 2026年1月 売上レポート ===
期間: 月次
月次レポートを生成しました。
--- 週次レポート ---
=== 2026年1月第1週 売上レポート ===
期間: 週次
週次レポートを生成しました。
|
無事に月次レポートと週次レポートの両方を生成できました!
コード例2: 問題点の確認
一見うまくいっているように見えますが、問題があります。
flowchart TD
A[create_report呼び出し] --> B{type の値は?}
B -->|monthly| C[MonthlyReport を生成]
B -->|weekly| D[WeeklyReport を生成]
B -->|daily| E[DailyReport を生成]
B -->|quarterly| F[QuarterlyReport を生成]
B -->|yearly| G[YearlyReport を生成]
B -->|その他| H[エラー: Unknown type]
style B fill:#ffcccc
style E fill:#ffffcc
style F fill:#ffffcc
style G fill:#ffffcc
この図のように、種別が増えるたびに分岐が増えていきます。
例えば、さらに「日次レポート」「四半期レポート」「年次レポート」が必要になったらどうなるでしょうか?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| sub create_report ($self, $type, $title) {
if ($type eq 'monthly') {
return MonthlyReport->new(title => $title);
}
elsif ($type eq 'weekly') {
return WeeklyReport->new(title => $title);
}
elsif ($type eq 'daily') {
return DailyReport->new(title => $title);
}
elsif ($type eq 'quarterly') {
return QuarterlyReport->new(title => $title);
}
elsif ($type eq 'yearly') {
return YearlyReport->new(title => $title);
}
else {
die "Unknown report type: $type";
}
}
|
問題点をまとめると
- if/elseがどんどん長くなる
- 新しいレポート種別を追加するたびに
ReportGeneratorを修正する必要がある - レポートの種類が増えると、コードが読みにくくなる
- 修正箇所が増えると、バグが入り込む可能性が高まる
これは「開放閉鎖原則」(Open-Closed Principle)に違反しています。
開放閉鎖原則: ソフトウェアの構成要素は「拡張に対しては開いていて、修正に対しては閉じている」べき
つまり、新しい機能を追加するときに、既存のコードを修正するのではなく、新しいコードを追加するだけで済むのが理想です。
今回のまとめ
今回は、週次レポートを追加するためにif/elseで種別を切り替える方法を試しました。
WeeklyReportクラスを新規作成したcreate_reportメソッドにtype引数を追加した- if/elseでレポートの種類を切り替えた
しかし、この方法には問題があります。
- if/elseが肥大化する
- 新しい種別を追加するたびに既存コードを修正する必要がある
- 開放閉鎖原則に違反している
次回以降で、この問題を段階的に解決していきます。
次回予告
次回は「レポートの共通ルールを決めよう」として、レポートクラス間で共通のルールを定義します。
Moo::Roleを使って、すべてのレポートが持つべきメソッドを強制する方法を学びましょう。