Featured image of post 「404だけ、IPとも組み合わせて」でクラスが掛け算に増える。継承で機能を足す限界【クラス爆発】

「404だけ、IPとも組み合わせて」でクラスが掛け算に増える。継承で機能を足す限界【クラス爆発】

IP フィルターに 404 検出を組み合わせようとすると、IPAndStatusFilteredLogParser のような組み合わせごとのクラスが必要になる。条件が増えるたびにクラス数が掛け算で増え、親の修正が全サブクラスに響きかねない問題を、実際のコードで確かめます。

@nqounetです。

前回は「継承」を使って、IPアドレスでログをフィルタリングする機能を追加しました。

今回は、新たな要望に応える中で「継承による機能拡張の限界」に直面します。

新たな要望:404エラーも見つけたい

「特定のIPアドレスだけを見たい」と言っていた上司が、今度はこう言ってきました。

「404 Not Foundのエラーが出ているログだけを抽出したいな。あ、もちろん前のIPアドレスフィルタリングと組み合わせて使うこともあるからね」

なるほど、ステータスコードが404のものを抽出する機能ですね。

継承でやってみる(悪い例)

素直に継承でやろうとすると、以下のクラスが必要になります。

  1. StatusFilteredLogParser: ステータスコードでフィルタリングするクラス
  2. IPAndStatusFilteredLogParser: IPアドレスかつステータスコードでフィルタリングするクラス

…おや?クラス名が長くなってきましたね。

コード例: IPAndStatusFilteredLogParser.pm

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package IPAndStatusFilteredLogParser;
use Moo;
extends 'IPFilteredLogParser'; # IPフィルタリング機能を継承

has target_status => ( is => 'ro', required => 1 );

sub next_log ($self) {
    # 親(IPフィルター)のnext_logを呼び出す
    while (defined(my $log = $self->SUPER::next_log)) {
        # さらにステータスコードで判定
        if ($log->{status} eq $self->target_status) {
            return $log;
        }
    }
    return undef;
}
1;

これを使えば、「特定のIP」かつ「特定のステータス」のログを抽出できます。

組み合わせ爆発(Class Explosion)

しかし、さらに要望が増えたらどうなるでしょうか?

  • 「レスポンスタイムが遅いログも抽出したい(SlowLogParser)」
  • 「特定のパスへのアクセスも抽出したい(PathLogParser)」

これらを自由に組み合わせようとすると、クラスの継承関係はどうなるでしょう?

  • IPFilteredLogParser
  • StatusFilteredLogParser
  • SlowLogParser
  • IPAndStatusFilteredLogParser
  • IPAndSlowFilteredLogParser
  • StatusAndSlowFilteredLogParser
  • IPAndStatusAndSlowFilteredLogParser
  • …

機能が1つ増えるたびに、必要なクラスの数が爆発的に増えていきます。これが「クラス爆発(Class Explosion)」と呼ばれる問題です。

さらに、「IPフィルタリングのロジックを変えたい」と思った時、IPFilteredLogParser だけでなく、それを継承している全てのクラス(IPAnd...)に影響が出るかもしれません。修正の影響範囲が読めなくなります。

解決策:コンポジション(組み合わせ)

この問題を解決するには、「継承(Is-A関係)」ではなく「コンポジション(Has-A関係)」を使う必要があります。

つまり、「IPフィルタリング機能を持つパーサー」という巨大なクラスを作るのではなく、「フィルタリングという機能(部品)を、動的にパーサーにくっつける」という発想の転換です。

これこそが、次回紹介するDecoratorパターンの考え方です。

次回予告

次回、いよいよDecoratorパターンが登場します。

継承の呪縛から解き放たれ、必要な機能をレゴブロックのように自由に組み合わせられる世界へ案内します!

第5回: 組み合わせごとのクラスをやめ、フィルターを包んで重ねる。Role と around で作る【Decorator】

comments powered by Disqus
システム開発・AIワークフローのご相談は Meetsource へ
Hugo で構築されています。
テーマ Stack は Jimmy によって設計されています。