GoFデザインパターン
こんにちは!パン君です。
今回はエンジニアの初心に戻って、GoF(Gang of Four)のデザインパターンについて整理・解説します。
筆者は固有名詞を覚えるのが苦手なので漏れや表現の揺らぎがあるかもしれませんが主要な考え方と実践上の注意点を明確に伝えます。
なお、事実関係の根拠は記事末に出典(URL と該当箇所引用)を載せます。
実務で採用する際は必ず一次情報(原典や信頼できる解説)を参照してください。
はじめに
GoF(Gang of Four)は 1994年に刊行された書籍 "Design Patterns: Elements of Reusable Object-Oriented Software" に由来する用語でオブジェクト指向設計における再利用可能なソリューション(パターン)をまとめたものです。GoF がまとめた 23 個のパターンは大きく以下の 3 カテゴリに分類されます。
- 生成(Creational): オブジェクトの生成方法を扱う
- 構造(Structural): クラスやオブジェクトの組み合わせ方・構成を扱う
- 振る舞い(Behavioral): オブジェクト間の通信やアルゴリズムの定義を扱う
これらは「答え」を与えるものではなく「設計時のひな型(テンプレート)」です。言語機能やフレームワークの進化で不要になる場合や、逆に過剰な適用で複雑化するリスクもあります。用途に合わせて、常に判断基準を持って採用してください。
根拠(抜粋):
- "A software design pattern describes a reusable solution to a commonly needed behavior in software." — Software design pattern (Wikipedia)
en.wikipedia.orgSoftware design pattern - Wikipedia
- "Design Patterns: Elements of Reusable Object-Oriented Software (1994) is a software engineering book describing software design patterns." — Design Patterns (Wikipedia)
en.wikipedia.orgDesign Patterns - Wikipedia
引用はあくまで概説の根拠です。採用・実装時は原著や具体的な言語実装例も確認してください。
各カテゴリの簡潔説明
生成(Creational)
オブジェクトをどのように作るかに関するパターン群です。
生成ロジックを分離することで、依存関係の管理やテスト容易性、拡張性を向上できます。構造(Structural)
クラスやオブジェクトをどう組み合わせてより大きな機能を作るかに関するパターン群です。
インターフェースの適合や責務分離、重複削減に寄与します。振る舞い(Behavioral)
オブジェクト間の責務分配ややり取り、アルゴリズムの入れ替えなど実行時の振る舞いに着目したパターン群です。
結合度の低減やテストしやすさを目的とします。
1. 生成パターン(Creational)
以下は代表的な 5つの生成パターンです。
要点、いつ使うか、注意点を短くまとめます。
シングルトン(Singleton)
| 項目 | 説明 |
|---|---|
| 要点 | インスタンスが1つしか持たないことを保証し、グローバルアクセスを提供する |
| 利点 | 共有リソースに対してアクセスを一元化できる |
| 欠点 | ライフサイクルの都合上テストが難しくなるケースがある。依存注入で代替できるケースもあるので検討 |
プロトタイプ(Prototype)
| 項目 | 説明 |
|---|---|
| 要点 | 既存のインスタンスを複製して新しいインスタンスを作成する |
| 利点 | コピーで初期化コストを下げる、動的なオブジェクト生成に向いている |
| 欠点 | 深いコピーや参照共有の扱いに注意 |
使用例
複雑な初期化を持つオブジェクトの複製
ファクトリメソッド(Factory Method)
| 項目 | 説明 |
|---|---|
| 要点 | サブクラスが生成する具体的なクラスを決定するためのメソッドを提供 |
| 利点 | クラス生成の拡張が容易で依存を抽象に向けることができるため、クライアントは生成の詳細を考慮する必要がなくなる |
| 欠点 | サブクラスの数が増えるとクラス階層が膨張することがある |
抽象ファクトリ(Abstract Factory)
| 項目 | 説明 |
|---|---|
| 要点 | 関連オブジェクト群を生成するためのインターフェースを提供 |
| 利点 | 互換性のあるオブジェクト群を一貫して生成でき実装の入れ替えが容易 |
| 欠点 | 新しい種類のオブジェクトを追加する場合、実装の変更範囲が大きくなる |
ビルダー(Builder)
| 項目 | 説明 |
|---|---|
| 要点 | 複雑なオブジェクト構築過程を分離、同じ構築手順で異なる表現を生成できる |
| 利点 | 可読性の高い構築コードを実現し段階的な設定が可能 |
| 欠点 | 生成過程が複雑な場合、ビルダーの実装が複雑になる |
使用例
可変なパラメータが多いドメインモデルや複数段階の初期化が必要なケース
2. 構造パターン(Structural)
以下は代表的な構造パターンです。
生成パターンのフォーマットに合わせて記述していきます。
アダプタ(Adapter)
| 項目 | 説明 |
|---|---|
| 要点 | 既存のクラスのインターフェースをクライアントが期待すrう別のインターフェースに変換するラッパーを提供 |
| 利点 | 既存コードや外部ライブラリを変更せずに新しいインターフェースと接続可能 |
| 欠点 | ラッパーが増えると可読性や保守コストが下がる |
使用例
古いAPIを新しいインターフェースへ適合させるケース
ブリッジ(Bridge)
| 項目 | 説明 |
|---|---|
| 要点 | 抽象と実装を分離し、それぞれを独立して拡張できるようにする |
| 利点 | 実装の切り替えや追加が容易になる |
| 欠点 | 構造が複雑になる。設計を最初に完結しておかないと過剰設計になることも |
使用例
プラットフォーム依存の実装を抽象から切り離すケース(例: 描画処理のバックエンド切り替え)
コンポジット(Composite)
| 項目 | 説明 |
|---|---|
| 要点 | 部分-全体のレイヤーを表現し、個別要素と複合要素を同一のインターフェースで扱う |
| 利点 | 再起的な構造を統一的に処理でき、クライアントコードが簡潔になる |
| 欠点 | 葉ノードと内部ノードの責務が曖昧になるケースがある |
使用例
UI コンポーネントやファイルシステム表現などの階層構造を表現するケース
デコレータ(Decorator)
| 項目 | 説明 |
|---|---|
| 要点 | オブジェクトに対して動的にモジュールを追加する。サブクラス化より柔軟に責務を組み合わせられる |
| 利点 | 既存オブジェクトを修正せずにモジュールを追加できる。組み合わせて複数の責務を付与可能 |
| 欠点 | 凸れーたがたそうになるとデバッグトレースが難しくなる |
使用例
ストリーム処理で圧縮や暗号化などをチェーンするケース
ファサード(Facade)
| 項目 | 説明 |
|---|---|
| 要点 | 複雑なサブシステムに対して簡潔で統一されたインターフェースを提供して利用を簡潔にする |
| 利点 | クライアントの使用を簡素化し内部実装の変更を隠蔽できる |
| 欠点 | ファサードのロジックを詰め込みすぎると責務が肥大化する |
使用例
複数のAPIを単一のサービスとしてまとめるケース
フライウェイト(Flyweight)
| 項目 | 説明 |
|---|---|
| 要点 | 多数の類似オブジェクトで共有可能な内部状態を切り出し、外部状態はクライアントが保持してメモリを節約 |
| 利点 | 大量オブジェクトのメモリ使用量を大幅に削減できる |
| 欠点 | 共有状態と被共有状態の管理が難しい |
使用例
テキストエディタの文字描画(フォント・グリフ情報を共有する等)
プロキシ(Proxy)
| 項目 | 説明 |
|---|---|
| 要点 | 実オブジェクトへの代理を提供し、アクセス制御、遅延初期化、リモートアクセスの抽象化を行う |
| 利点 | 透明な代理でアクセスを制御したり、リソースを節約できる |
| 欠点 | オーバーヘッドや透過性の低下、デバッグ時に実体と代理の違いが混乱を招く |
使用例
遅延ロードする or マッピングの参照、リモートオブジェクトへのスタブ
構造パターンは間接化(インデレクション)を増やすため、可読性や実行コストへの影響を常に考えたほうがいいです。
3. 振る舞いパターン(Behavioral)
以下は代表的な振る舞いパターンを同じフォーマットでまとめます。
チェーン・オブ・リスポンシビリティ(Chain of Responsibility)
| 項目 | 説明 |
|---|---|
| 要点 | 複数のハンドラをチェーンし、リクエストを順に試行して処理可能なハンドラに委譲する |
| 利点 | 送信者と受信者のカップリングを減らし、処理の追加や順序変更が容易 |
| 欠点 | デバッグ時にどのハンドラが処理したか追跡しづらくなる。最後まで処理されないケースに注意 |
使用例
ログ処理やリクエストフィルタチェーン
コマンド(Command)
| 項目 | 説明 |
|---|---|
| 要点 | 操作をオブジェクトとしてカプセル化します |
| 利点 | 操作の抽象化により各動作、操作の実行が容易になる |
| 欠点 | ? |
使用例
GUI のアクション管理(undo/redo)
インタプリタ(Interpreter)
| 項目 | 説明 |
|---|---|
| 要点 | 実装言語とは異なるフォーマットでオブジェクト構造で表現、それを解釈するための仕組みを提供 |
| 利点 | プログラムを外部から動的に制御可能になる |
| 欠点 | メンテナンスや実装が難しく、更にパフォーマンス面で理解と配慮が必要になってくる |
使用例
数式評価や簡易スクリプト言語、独自の文字列フォーマット
イテレータ(Iterator)
| 項目 | 説明 |
|---|---|
| 要点 | 集約オブジェクトの要素を逐次アクセスする抽象を提供し、内部表現を隠蔽 |
| 利点 | 集約の多様な実装に対して一貫した反復処理を提供 |
| 欠点 | 走査中の同時変更に対する設計が必要 |
使用例
コレクション走査やストリーム処理
メディエータ(Mediator)
| 項目 | 説明 |
|---|---|
| 要点 | 多数のオブジェクト間の相互作用を仲介者に集約して相互依存を減らす |
| 利点 | 個々のクラスを単純化し結合度を下げられる |
| 欠点 | 仲介者が複雑化すると集中化されたロジックがボトルネックになったり補修困難に陥る |
使用例
UIコンポーネント間の調停、チャットルームのメッセージ配信
メメント(Memento)
| 項目 | 説明 |
|---|---|
| 要点 | オブジェクトの内部状態を外部に保存し、復元できるようにする |
| 利点 | カプセル化けを保ちつつ状態を保存できる |
| 欠点 | 状態のコピーのコストや保存の管理が必要 メモリに注意 |
使用例
テキストエディタのundoスタック
オブザーバ(Observer / Publish-Subscribe)
| 項目 | 説明 |
|---|---|
| 要点 | あるオブジェクトの状態変化を複数の登録オブジェクトに通知する |
| 利点 | 一対多の通知を簡潔に実装でき、疎結合を実現できる |
| 欠点 | イベントの順序やスレッド安全性、解除忘れによるメモリリークに注意 |
使用例
イベント駆動UI、リアルタイム更新の購読モデル
ステート(State)
| 項目 | 説明 |
|---|---|
| 要点 | オブジェクトの内部状態に応じて振る舞いを状態オブジェクトに委譲、状態遷移を明確にする |
| 利点 | 複雑な条件分岐を状態ごとの振る舞いに分割して整理できる |
| 欠点 | 状態クラスが増えると管理が必要、ドキュメントなどにシーケンスと関係を明記すると良い |
使用例
ワークフローや通信プロトコルの状態管理
ストラテジー(Strategy)
| 項目 | 説明 |
|---|---|
| 要点 | アルゴリズム群をカプセル化、実行時に切り替え可能にする |
| 利点 | アルゴリズムを独立テスト、拡張が可能になり条件分岐が減る |
| 欠点 | 単純なケースで過剰抽象になるkとがある |
使用例
ソートや課金計算などのアルゴリズム切り替え
テンプレート・メソッド(Template Method)
| 項目 | 説明 |
|---|---|
| 要点 | アルゴリズムの骨格をスーパークラスに定義、詳細スナップをサブクラスで実装する |
| 利点 | 共通処理を再利用しつつ差分のみを実装可能 |
| 欠点 | サブクラス間の依存が強くなり、拡張の柔軟性に制約が出る可能性がある |
使用例
処理パイプラインやシーケンス化された処理フロー
ビジター(Visitor)
| 項目 | 説明 |
|---|---|
| 要点 | オブジェクト構造を変更せず、その構造に対する新しい操作を追加できる |
| 利点 | 新しい操作を追加しやすく、オブジェクト構造をオープンに保てる |
| 欠点 | エレメント側のインターフェースを拡張する必要があり、エレメント構造の変更時に更新する必要がある |
使用例
構文木に対する解析・最適化や型検査処理
実務での採用に際する注意点
- 本当にパターンが必要かをまず考える
問題がパターンの対象でなければ導入は不要。
シンプルな実装で十分な場合が多い。 - 言語やフレームワークの機能で代替できないか確認する
関数型言語や動的言語では GoF の一部パターンが不要または簡潔に実現できることがある。 - テスト性と依存関係を優先する
グローバル状態や密結合を生むパターンには注意する。 - ドキュメント化する
どのパターンを何故採用したかを README/設計書に残す。
将来のリファクタやレビューで重要。 - パフォーマンス/メンテナンスのトレードオフを理解する
間接化は設計の柔軟性をもたらすが、呼び出しコストや理解コストが増える可能性がある。
まとめ
GoFのデザインパターンは設計の有力なツールではありますが、鵜呑みにせず「 適切に 」利用することが重要です。
各パターンの目的を理解し言語特性や既存のフレームワークと照らし合わせて利用してください。
設計は経験と文脈に依存します。
パターンは「設計の語彙」を増やすためのものであり、それ自体が目的ではありません。
まずは小さい設計判断から適用してチームでナレッジの共有をすることが大事だと考えます。
(まぁ今の時代これを知らないことはほとんど無いとは思いますが)
次回はそれぞれのパターンの細かい利用ケース等をコードベースで説明してみたいと思います。
コメントを読み込み中...