GoFデザインパターンは現代でも有用か(4) — 現代でも有用なパターンとまとめ

この記事は「GoFデザインパターンは現代でも有用か — Go言語から再考する」シリーズの最終回(全4回)です。前回: 「GoFデザインパターンは現代でも有用か(3) — 言語に吸収されたパターンと非推奨のSingleton

Cover
Table of Contents

前回までの振り返り

第1回では、OOPのパラダイムシフトとGoFデザインパターンの誕生、そしてGo言語による脱OOPの流れを振り返りました。

第2回では、Go言語の3つの設計方針がGoFの23パターンのうち10パターンを不要にしたことを検証しました。

第3回では、残る13パターンのうち、言語機能として取り込まれたIteratorと、現代の設計手法により非推奨となったSingletonを検討しました。

分類パターン状態
OOPの制約への処方箋(第2回)Template Method, Factory Method, Abstract Factory, Bridge, Prototype, Memento, Strategy, Command, Visitor, Observer不要
言語機能として吸収(第3回)Iterator意識不要
設計手法の進化で非推奨(第3回)Singleton非推奨

本記事では、残る11のパターンがなぜ現代でも有用なのかを分析し、シリーズ全体の結論としてデザインパターンの本質的価値を考察します。

現代でも有用なパターン

残る11のパターンは、継承と参照セマンティクスのいずれにも依存していません。ここまで見てきたパターンがOOPの言語メカニズムへの処方箋だったのに対し、これらはOOPとは無関係に存在する、ソフトウェア設計の普遍的な課題を解決するものです。つまり、GoFの23パターンの中にはOOPの処方箋と、言語に依存しない設計の原則「ソフトウェアアーキテクチャの原則」が混在していたのです。

生き残ったパターンを、その理由ごとに分類します。

小さな部品を合成して大きな振る舞いを作る

Go言語の設計思想「継承より合成(composition over inheritance)」と直結する分類です。小さな部品を組み合わせて複雑な構造や振る舞いを構築するという原則は、プログラミングパラダイムを問わず有効です。

  • Composite — 再帰的なツリー構造を合成し、個々の要素と合成要素を同一視する。ファイルシステムやUIコンポーネントなど、階層構造を持つあらゆる領域で使われる
  • Decorator — 既存の機能に新しい責務を動的に重ね合わせる。Go言語ではhttp.Handlerをラップするミドルウェアパターンとして標準的に使われている
  • Chain of Responsibility — 処理を連鎖させ、リクエストを適切なハンドラに委譲する。Go言語のHTTPミドルウェアチェーンそのもの
  • Builder — 複雑なオブジェクトを段階的に構築する。Go言語ではfunctional optionsパターンとして定着している

コンポーネント間の境界を制御する

ソフトウェアの規模が大きくなると、コンポーネント間の依存関係やインターフェースの不一致が複雑さの主要因になります。この問題に対処するパターンは、言語やパラダイムに関係なく必要です。

  • Adapter — 互換性のないインターフェース同士を橋渡しする。外部ライブラリやレガシーシステムとの統合で常に必要になる
  • Facade — 複雑なサブシステムに対して統一的な窓口を提供する。Go言語ではパッケージの公開APIを絞ることで自然に実現される
  • Proxy — 対象へのアクセスを制御する代理を提供する。遅延初期化、アクセス制御、ログ記録など用途が幅広い
  • Mediator — 多対多のオブジェクト間の相互作用を仲介者に集約し、疎結合にする。イベントバスやメッセージブローカーの設計に通じる

状態に応じて振る舞いを切り替える

  • State — 内部状態に応じてオブジェクトの振る舞いを変更する。Go言語ではStateインターフェースを定義し、状態ごとに異なる型で実装する。有限状態マシンの設計に直結する

特定領域の問題を解決する

  • Flyweight — 多数の細粒度オブジェクトの共有によりメモリ使用量を削減する。文字列のインターン化やキャッシュなど、メモリ最適化が必要な場面で有効
  • Interpreter — 言語の文法を定義し解釈する。DSL(ドメイン固有言語)や設定ファイルのパーサー、式評価エンジンなど、特定の用途で使われる

まとめ — デザインパターンの本質的価値とは何だったのか

GoFの功績は、コード内で繰り返し現れるパターンに名前を付け、開発者の共通言語を確立したことにあります。「ここはObserverで」「Adapterを挟もう」と言えば設計意図が伝わる。この語彙の確立は、ソフトウェア開発の生産性に大きく貢献しました。

しかし、本記事の分析が示すように、GoFの23パターンには性質の異なる2種類のものが混在していました。

  • 実装の工夫 : パラダイムシフトを起こしたばかりのOOPの未成熟さをカバーするもの。継承の副作用を管理するTemplate MethodやVisitor、参照セマンティクスを補完するPrototypeやMemento。これらはOOPという特定のパラダイムの制約の中で必要だった処方箋であり、パラダイムが変われば不要になるもの。
  • 普遍的な設計原則 - 合成による構築、境界の制御など設計のベストプラクティス。CompositeやDecorator、AdapterやFacadeは、OOPとは無関係に、ソフトウェアの複雑さに対処するためのアーキテクチャの知恵。

GoFの問題は、この2つがあまり区別されず、同列にカタログ化されたことです。そして当時、OOPが成熟した段階を経験したプログラマが少なかったことも相まって、GoFは必要以上に神格化されました。 本来はコードレベルの実装パターンであるにもかかわらず、当時は業務システムのアーキテクチャを語る場面でGoFのパターン名が持ち出されるような頓珍漢なエンジニアさえ出現しました。デザインパターンは「設計パターン」と訳されますが、実態は「コードパターン」であったことに留意すべきです。

Go言語が示したのは、OOPの制約に起因するパターンは言語設計で解消できるということです。そして残った11のパターンこそが、GoFの本質的な遺産「言語やパラダイムを超えた設計原則」だったと言えます。

ただし、この結論すら永続的なものではありません。Rustの列挙型とパターンマッチングはStateパターンを言語機能として吸収しつつありますし、名前付き引数やデフォルト値を持つ言語ではBuilderの必要性が薄れます。今日「普遍的な設計原則」に見えるものも、将来の言語やパラダイムの進化によって、さらに整理されていく可能性があります。

デザインパターンの本質的な価値は、23のカタログそのものにはありませんでした。ソフトウェアの複雑さに名前を付け、共有可能な知識として体系化しようとした試み「その営み」こそが、GoFの真の貢献だったのではないでしょうか。

本シリーズで取り上げたGoFの23パターンすべてをGo言語で実装したサンプルコードを公開しています。