SOFTWARE DESIGN / GANG OF FOUR / REUSABLE PATTERNS
GoFのデザインパターンとは? 生成・構造・振る舞いの23パターンを体系的に理解する
公開日:2026-10-13
「送料計算の方法が増えるたび、決済画面を修正する」「通知先が増えるたび、注文処理が複雑になる」。設計パターンは、こうした変更の影響を小さくする考え方です。
変更に弱い設計 → GoFとは → 3分類と23種類 → 4つの実装例 → 類似パターン → アーキテクチャパターン → セキュリティ設計 → 操作ラボ
1. なぜデザインパターンを学ぶのか
ECサイトでは、通常配送・速達・店舗受取によって送料計算のルールが異なります。画面、支払い、発送などの処理に条件分岐を直接書き続けると、一か所の修正が広範囲に影響します。変化しやすい箇所を切り出してインターフェースで接続することで、既存の処理を変えずに拡張しやすくなります。
デザインパターンは使うだけで安全・高速になる魔法ではありません。「よく起こる問題」「一般化できる解決方法」「その代償」をまとめた設計上の選択肢です。パターンを使わない方が分かりやすい小規模コードも少なくありません。
2. GoFの由来と3分類
GoFは Gang of Four(4人組)の略称です。Erich Gamma、Richard Helm、Ralph Johnson、John Vlissidesによる1994年の書籍『Design Patterns: Elements of Reusable Object-Oriented Software』で体系化された23の設計パターンを指します。
生成
どの実装を作る?
Factory Method / Builder
構造
どう部品をつなぐ?
Adapter / Decorator
振る舞い
どう動作を分担する?
Strategy / Observer
ノードは設計概念の関係を示します。ネットワーク通信の経路ではありません。
生成(Creational) 5種類
作り方と生成責務を分ける
構造(Structural) 7種類
部品を組み合わせ、インターフェースを整理する
振る舞い(Behavioral) 11種類
処理の選択と責務分担を整理する
3. Strategy:送料計算を選んで差し替える
Strategyは、同じ目的を持つ計算方法を共通の契約で呼び出せるようにする振る舞いのパターンです。注文画面は送料計算の詳細を知らなくてもよく、配送方法を変えるだけで振る舞いを交換できます。
fee(subtotal): number
interface ShippingStrategy {
fee(subtotal: number): number;
}
class StandardShipping implements ShippingStrategy {
fee(subtotal: number) { return subtotal >= 5000 ? 0 : 550; }
}
class ExpressShipping implements ShippingStrategy {
fee(subtotal: number) { return subtotal >= 10000 ? 450 : 1100; }
}
function checkout(subtotal: number, shipping: ShippingStrategy) {
return subtotal + shipping.fee(subtotal);
}
checkout(4200, new StandardShipping()); // 4750
checkout(4200, new ExpressShipping()); // 5300実装ごとに独立してテストできる一方、わずかな分岐のために多数のクラスを導入すれば読みづらくなります。TypeScriptでは関数を引数として渡す方法でも同じ設計意図を実現できる場合があります。
4. Observer:注文イベントを購読者に通知
Observerは、ある状態変化に対して複数の購読者が反応する振る舞いのパターンです。注文が確定したら「メールを送る」「在庫の表示を更新する」「ログを残す」といった処理を、注文ロジックに密結合させずに追加できます。
Publisher
type Listener = (orderId: string) => void;
class OrderEvents {
private listeners = new Set<Listener>();
subscribe(listener: Listener) {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
confirmed(orderId: string) {
this.listeners.forEach(listener => listener(orderId));
}
}
const events = new OrderEvents();
const unsubscribe = events.subscribe(id => console.log(id));
events.confirmed("order-1042");
unsubscribe();5. Factory Method:生成の仕組みを拡張する
Factory Methodは、オブジェクトを生成するメソッドを基底クラスで定義し、具体的な生成対象をサブクラスで決められるようにする生成パターンです。カード決済と銀行振込を考えると、処理の利用方法は同じでも生成するオブジェクトは異なります。
interface Payment {
pay(amount: number): string;
}
class CardPayment implements Payment {
pay(amount: number) { return "カード決済 " + amount; }
}
class BankPayment implements Payment {
pay(amount: number) { return "銀行振込 " + amount; }
}
abstract class PaymentFlow {
protected abstract createPayment(): Payment;
run(amount: number) { return this.createPayment().pay(amount); }
}
class CardFlow extends PaymentFlow {
protected createPayment() { return new CardPayment(); }
}
class BankFlow extends PaymentFlow {
protected createPayment() { return new BankPayment(); }
}
new CardFlow().run(5000);単一の関数が引数に応じて実装を返す「シンプルファクトリー」と、継承に基づく厳密なFactory Methodは区別しましょう。関連する複数の製品を組として生成したいならAbstract Factoryも候補になります。
6. Adapter:新旧システムのインターフェースを合わせる
Adapterは、古いAPIの戻り値やメソッド名が新システムの期待と異なる場合、変換する薄い層を置く構造パターンです。古いシステム自体を書き換えずに、新しい形式から扱えるようにします。
class LegacyOrderApi {
read(): string { return "ORDER-1042"; }
}
interface OrderGateway {
load(): { orderId: string; source: string };
}
class LegacyOrderAdapter implements OrderGateway {
constructor(private legacy: LegacyOrderApi) {}
load() {
return { orderId: this.legacy.read(), source: "legacy" };
}
}
const gateway: OrderGateway =
new LegacyOrderAdapter(new LegacyOrderApi());
gateway.load();7. 名前が似ているパターンの区別
| 比較 | 違い | 例 |
|---|---|---|
| Strategy / State | 交換できるアルゴリズム / 内部状態に応じて振る舞いが変わる | 送料計算 / 注文ステータスの遷移 |
| Adapter / Facade | インターフェースを適合 / 複雑な機能に簡潔な窓口を提供 | 旧API変換 / 複数の決済APIをまとめる |
| Decorator / Proxy | 既存オブジェクトに責務を付加 / アクセスを仲介・制御 | 実行時間の計測追加 / アクセス制御付きの代理 |
| Factory Method / Abstract Factory | 生成メソッドの拡張 / 関連する製品群の整合した生成 | 決済クラスの選択 / UI部品セットの生成 |
8. アーキテクチャパターンや言語の慣用設計との違い
ソフトウェア設計には、クラス単位より大きなシステム構造を扱うパターンもあります。たとえばPOSA(Pattern-Oriented Software Architecture)で知られるLayersやBrokerは、コンポーネントやシステム全体の配置・協調を考えるアーキテクチャパターンです。GoFの構造パターンと混同してはいけません。
| 設計の対象 | 主な例 | 粒度 |
|---|---|---|
| GoFデザインパターン | Strategy、Adapter、Observer | オブジェクト・クラスの責務 |
| アーキテクチャパターン | Layers、Broker、Pipes and Filters、Microkernel | コンポーネント・システム構造 |
| 言語・環境のイディオム | 言語固有の引数処理や可変性制御 | 実装慣習 |
Layersも「構造」に関わりますが、GoFの7つの構造パターンには入りません。分類の名前だけでなく、パターンを提示した体系と対象とする設計レベルを確認することが大切です。
9. セキュリティ開発への応用
デザインパターンはセキュリティ制御そのものではありません。しかし、認証方式の交換、監査ログの追加、外部認証サービスの接続などで、責務の境界を整理するのに役立ちます。
| パターン | 応用例 | 注意事項 |
|---|---|---|
| Strategy | リスク評価・認証方式の選択 | 弱い方式への意図しない切替を防止 |
| Adapter | 外部認証APIの差異を吸収 | 署名検証・入力検証を省略しない |
| Decorator | 監査記録や性能計測を追加 | ログに秘密情報を含めない |
| Proxy | 保護対象の操作前に認可を確認 | 代理層を迂回する経路を残さない |
| Observer | 不審な振る舞いを複数の監視処理へ通知 | 配信保証・再送・重複を別途設計 |
10. 使い過ぎを避ける判断基準
「これから増えそう」という予測だけでクラスを大量追加しないようにします。まずいま何が変わり、何が変わらないかを特定しましょう。処理が少数で固定されているなら、単純な関数や条件分岐で十分な場合があります。抽象化によってテストと変更が容易になるか、メンテナンスする人が理解できるかが採用の判断軸です。
11. 操作ラボ:4つの設計意図を体験
送料の算出アルゴリズム、イベントの購読者、決済実装の生成、古いAPIへの接続をそれぞれ操作できます。「設計パターンが解決する問題」が異なることを確認してください。
INTERACTIVE DESIGN LAB
設計パターンを操作して違いを体感する
ECサイトの注文処理を題材に、置き換える・通知する・生成する・つなぐ、という4つの設計上の目的を比べます。ブラウザー内だけの教育用シミュレーションです。
Strategy:送料計算アルゴリズムを交換
注文処理の呼び出し方は共通にして、選んだ配送方法に応じて送料計算だけを入れ替えます。
送料
550円
合計
4,750円
const fee = strategies["standard"](subtotal);
// 550円:利用側は計算方式を知らないイベント/実行ログ
操作するとここに履歴が表示されます。
12. まとめ
生成:オブジェクトの作り方を分離する。
構造:異なる部品をつなぎ、構成を単純にする。
振る舞い:処理の選択・通知・責務を整理する。
23の名前を覚えるだけではなく、変更が必要な場所をどのように閉じ込めるか考えるのが設計パターンを学ぶ目的です。
参考資料
- Design Patterns: Elements of Reusable Object-Oriented Software— Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides
- The Catalog of Design Patterns— Refactoring.Guru
- Patterns in Enterprise Software— Martin Fowler
- Patterns of Enterprise Application Architecture— Martin Fowler
- TypeScript Handbook — Interfaces— Microsoft