株式会社ジオデータ開発 ロゴ
株式会社ジオデータ開発GEO DATA DEVELOPMENT Inc.
設計パターンGoFオブジェクト指向TypeScriptStrategyObserverFactory MethodAdapter

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の設計パターンを指します。

設計課題を3つの方向から考える
変更に強いオブジェクト指向設計

生成

どの実装を作る?

Factory Method / Builder

構造

どう部品をつなぐ?

Adapter / Decorator

振る舞い

どう動作を分担する?

Strategy / Observer

ノードは設計概念の関係を示します。ネットワーク通信の経路ではありません。

生成(Creational) 5種類

作り方と生成責務を分ける

Abstract FactoryBuilderFactory MethodPrototypeSingleton

構造(Structural) 7種類

部品を組み合わせ、インターフェースを整理する

AdapterBridgeCompositeDecoratorFacadeFlyweightProxy

振る舞い(Behavioral) 11種類

処理の選択と責務分担を整理する

Chain of ResponsibilityCommandInterpreterIteratorMediatorMementoObserverStateStrategyTemplate MethodVisitor

3. Strategy:送料計算を選んで差し替える

Strategyは、同じ目的を持つ計算方法を共通の契約で呼び出せるようにする振る舞いのパターンです。注文画面は送料計算の詳細を知らなくてもよく、配送方法を変えるだけで振る舞いを交換できます。

Strategy:呼び出す側から計算方法を分離
注文画面(Checkout)
ShippingStrategy
fee(subtotal): number
通常配送
速達配送
店舗受取
TypeScript|Strategy
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
→
メール
在庫更新
監査ログ
TypeScript|Observer
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は、オブジェクトを生成するメソッドを基底クラスで定義し、具体的な生成対象をサブクラスで決められるようにする生成パターンです。カード決済と銀行振込を考えると、処理の利用方法は同じでも生成するオブジェクトは異なります。

TypeScript|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の戻り値やメソッド名が新システムの期待と異なる場合、変換する薄い層を置く構造パターンです。古いシステム自体を書き換えずに、新しい形式から扱えるようにします。

TypeScript|Adapter
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の名前を覚えるだけではなく、変更が必要な場所をどのように閉じ込めるか考えるのが設計パターンを学ぶ目的です。

参考資料