@nqounetです。
前回は通知先を増やそうとしてコードが破綻する様子を見ました。1つのループで「ログ出力」「スコア計算」「アラート」と何でもやろうとして、修正が大変になる未来が見えましたね。
今回はこの問題を解決するため、通知担当を別クラスに分離します。
分離の方針
現在のコードでは、1つのループ内で複数の処理を行っています。
1
2
3
4
5
6
| for my $event (@events) {
# ログ出力
# スコア計算
# アラートチェック
# ...全部ここに詰め込み
}
|
これを「担当者ごとに分ける」という発想で改善します。
classDiagram
class IntrusionEvent {
+timestamp
+source_ip
+attack_type
}
class RadarLogObserver {
+update(event)
}
class ThreatScoreObserver {
+total_score
+update(event)
}
IntrusionEvent <-- RadarLogObserver : receives
IntrusionEvent <-- ThreatScoreObserver : receives
- ログ担当: 侵入イベントをコンソールに表示する
- スコア担当: 脅威スコアを計算して累計を管理する
それぞれの担当は、自分の仕事だけに集中します。
ログ担当クラスを作る
まず、ログ出力だけを担当するクラスを作りましょう。
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
| #!/usr/bin/env perl
# 言語: perl
# バージョン: 5.36以上
# 依存: Moo(cpanmでインストール)
use v5.36;
package IntrusionEvent;
use Moo;
has timestamp => (
is => 'ro',
required => 1,
);
has source_ip => (
is => 'ro',
required => 1,
);
has attack_type => (
is => 'ro',
required => 1,
);
package RadarLogObserver;
use Moo;
sub update ($self, $event) {
say "=== 侵入イベント検知 ===";
say "時刻: " . $event->timestamp;
say "発信元: " . $event->source_ip;
say "攻撃種別: " . $event->attack_type;
say "";
}
package ThreatScoreObserver;
use Moo;
my %threat_scores = (
'SSH Brute Force' => 80,
'Port Scan' => 30,
'SQL Injection Attempt' => 90,
'XSS Attack' => 70,
);
has total_score => (
is => 'rw',
default => 0,
);
sub update ($self, $event) {
my $score = $threat_scores{$event->attack_type} // 50;
$self->total_score($self->total_score + $score);
say "[脅威スコア] $score 加算 (累計: " . $self->total_score . ")";
}
package main;
# 通知担当を用意
my $log_observer = RadarLogObserver->new;
my $score_observer = ThreatScoreObserver->new;
# イベントを作成
my @events = (
IntrusionEvent->new(
timestamp => '2026-01-18T06:00:00+09:00',
source_ip => '192.168.1.100',
attack_type => 'SSH Brute Force',
),
IntrusionEvent->new(
timestamp => '2026-01-18T06:01:15+09:00',
source_ip => '10.0.0.55',
attack_type => 'Port Scan',
),
);
# 各イベントを処理
for my $event (@events) {
$log_observer->update($event);
$score_observer->update($event);
}
|
実行結果はこうなります。
1
2
3
4
5
6
7
8
9
10
11
12
| === 侵入イベント検知 ===
時刻: 2026-01-18T06:00:00+09:00
発信元: 192.168.1.100
攻撃種別: SSH Brute Force
[脅威スコア] 80 加算 (累計: 80)
=== 侵入イベント検知 ===
時刻: 2026-01-18T06:01:15+09:00
発信元: 10.0.0.55
攻撃種別: Port Scan
[脅威スコア] 30 加算 (累計: 110)
|
何が良くなったのか
前回のコードと比べて、大きく改善した点があります。
改善1: 責務が分離された
RadarLogObserver: ログ出力だけを担当ThreatScoreObserver: スコア計算だけを担当
それぞれのクラスは「自分の仕事」に集中しています。これが**単一責任の原則(SRP)**です。
改善2: 共通のインターフェース
どちらのクラスもupdateというメソッドを持っています。これにより、イベント処理のループがシンプルになりました。
1
2
3
4
| for my $event (@events) {
$log_observer->update($event);
$score_observer->update($event);
}
|
新しい通知先を追加したくなったら、updateメソッドを持つ新しいクラスを作れば良いのです。
今回のまとめ
今回は通知担当を別クラスに分離することで、コードの責務を明確にしました。
RadarLogObserver: ログ出力専門ThreatScoreObserver: スコア計算専門- 共通の
updateメソッドで統一的に呼び出せる
これで各クラスは自分の仕事に集中でき、変更の影響範囲も限定されます。
次回予告
次回は「このupdateメソッド、ちゃんと持っていることを保証したい」という話です。Moo::Roleを使って、通知担当が満たすべき契約を定義します。
インターフェースの力を感じてください。