Podcastで聞いた「GoFの再構成案」が目からウロコだった話
技術系Podcastでデザインパターンの話題が出ていました。「今さらGoF?」と最初は思いましたが、聴いてみると予想外の面白さでした。出版から15年後に著者たち自身が発表した「再構成案(Revised GoF)」についての話です。
継承は密結合の元凶
かつては「再利用といえば継承」と教わりましたが、実際の開発では親クラスと子クラスが強く結びつきすぎて苦労することがよくあります。
Podcastでは8段継承のような「スパゲッティコード」の弊害が語られていました。著者たちも、継承は実装を使い回す便利な仕組みというより、親と子を固定する鎖になりがちだと考えているそうです。「継承より委譲(Delegation)」や「コンポジション(Composition)」が現代の正解だという話には深く頷けました。
守るべきは呼び出し側
「再利用」の捉え方も目からウロコでした。クラスのロジックを別の場所で使い回すこと(実装の再利用)ではなく、**「利用側のコードを守る」**ことが本来の目的だそうです。
たとえばIteratorパターンなら、中身がArrayでもMapでも、呼び出し側はただ next() を呼ぶだけで済みます。中身の実装が変わっても、呼び出し側のコードは書き換えなくていい。「インターフェースに対してプログラミングせよ」というのは、実装の変更から利用側を保護するという意味だったのかと、ようやく腹落ちしました。
パターンは「事前の計画」ではなく「進化の道標」
設計の最初から「ここはStrategyパターンで」と予見して柔軟な構造を作り込んでも、実際の仕様変更には役に立たない(YAGNI)ことがよくあります。
現代的なアプローチは、まずシンプルに書き、TDD(テスト駆動開発)で進め、リファクタリングの結果としてパターンに落とし込むのが理想とのこと。デザインパターンは**「設計の出発点」ではなく「リファクタリングの目的地(ゴール)」**として機能するという考え方は、私の開発スタイルを変える大きなヒントになりました。
現代版GoFの顔ぶれ
著者たちが2009年頃に再定義したリストは、以下のようになっています。
1軍:Core(これだけは知っておきたい)
Composite, Strategy, State, Command, Iterator, Proxy, Template Method, Facade
2軍:Creational(生成系)
Factory, Prototype, Builder
[NEW] Dependency Injection (DI)
3軍:Peripheral(その他)
Abstract Factory, Visitor, Decorator...
[NEW] Null Object
リストラされたパターンたち
Singleton, Adapter, Bridge, Chain of Responsibility, Memento, Observer
かつて定番だったSingletonやObserverがリストから削除されているのは衝撃的です。Singletonは「テストしにくく、実質的なグローバル変数」であるため、現代の感覚では推奨されないようです。また、Abstract FactoryもDIコンテナの普及によって重要性が低下したとされています。
DIとNull Objectの昇格
代わりに Dependency Injection (DI) が生成系パターンとして正式に追加されました。
DIを使わない場合、プログラムのエントリーポイント(Main)がすべてのオブジェクト生成の知識を一手に引き受ける「汚れ役」になります。依存関係が複雑になるほど、DIコンテナによる自動化が不可欠になる話は納得感がありました。
また、 if (obj != null) 連打を避ける Null Object も追加されています。「何もしないオブジェクト」を渡すことで、条件分岐をポリモーフィズムで解決するこの手法は、コードを劇的にシンプルにします。
// 何もしないLoggerを渡しておけば、nullチェック不要でコードがシンプルになる
const logger = new NullLogger();
logger.log("log");
感想
将来を予見して重厚な城を築くより、いつでも動ける身軽さを選ぼうということなのかもしれません。デザインパターンは必殺技ではなく、エンジニア同士の 「共通言語」 として使うのが一番便利です。「ここはStrategyっぽくしました」の一言で意図が伝わるなら、それだけで学ぶ価値は十分にあります。
この再構成案を頭の片隅に置いて、まずは「リファクタリングの第2版」あたりを読み直してみようと思いました。