@nqounetです。
前回は「継承」を使って、IPアドレスでログをフィルタリングする機能を追加しました。
今回は、新たな要望に応える中で「継承による機能拡張の限界」に直面します。
新たな要望:404エラーも見つけたい
「特定のIPアドレスだけを見たい」と言っていた上司が、今度はこう言ってきました。
「404 Not Foundのエラーが出ているログだけを抽出したいな。あ、もちろん前のIPアドレスフィルタリングと組み合わせて使うこともあるからね」
なるほど、ステータスコードが404のものを抽出する機能ですね。
継承でやってみる(悪い例)
素直に継承でやろうとすると、以下のクラスが必要になります。
StatusFilteredLogParser: ステータスコードでフィルタリングするクラスIPAndStatusFilteredLogParser: IPアドレスかつステータスコードでフィルタリングするクラス
…おや?クラス名が長くなってきましたね。
コード例: IPAndStatusFilteredLogParser.pm
| |
これを使えば、「特定のIP」かつ「特定のステータス」のログを抽出できます。
組み合わせ爆発(Class Explosion)
しかし、さらに要望が増えたらどうなるでしょうか?
- 「レスポンスタイムが遅いログも抽出したい(SlowLogParser)」
- 「特定のパスへのアクセスも抽出したい(PathLogParser)」
これらを自由に組み合わせようとすると、クラスの継承関係はどうなるでしょう?
IPFilteredLogParserStatusFilteredLogParserSlowLogParserIPAndStatusFilteredLogParserIPAndSlowFilteredLogParserStatusAndSlowFilteredLogParserIPAndStatusAndSlowFilteredLogParser- …
機能が1つ増えるたびに、必要なクラスの数が爆発的に増えていきます。これが「クラス爆発(Class Explosion)」と呼ばれる問題です。
さらに、「IPフィルタリングのロジックを変えたい」と思った時、IPFilteredLogParser だけでなく、それを継承している全てのクラス(IPAnd...)に影響が出るかもしれません。修正の影響範囲が読めなくなります。
解決策:コンポジション(組み合わせ)
この問題を解決するには、「継承(Is-A関係)」ではなく「コンポジション(Has-A関係)」を使う必要があります。
つまり、「IPフィルタリング機能を持つパーサー」という巨大なクラスを作るのではなく、「フィルタリングという機能(部品)を、動的にパーサーにくっつける」という発想の転換です。
これこそが、次回紹介するDecoratorパターンの考え方です。
次回予告
次回、いよいよDecoratorパターンが登場します。
継承の呪縛から解き放たれ、必要な機能をレゴブロックのように自由に組み合わせられる世界へ案内します!
