2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

いまさら整理したGoF 23パターン【Java実装付き】

2
Posted at

はじめに

「デザインパターンって名前は知ってるけど、実務でどう使うの?」
「そもそもデザインパターンって今でも有効?」
「業務で利用してるこれってなんかパターン化されている気がするけどなんて説明するのがいいのかな?」

そんな疑問を持ったことはありませんか?

GoF(Gang of Four)の23種のパターンは、よく利用されるオブジェクト指向のパターンを分類したものです。設計の共通言語として非常に強力ですが、抽象的な説明だけでは実務に活かしにくい(というか伝わらない)のが正直なところです。
※私も初めてのプロジェクトで、かなり悩みました。

この記事では、23種すべてを 生成・構造・振る舞い の3グループに分けて整理してみました。「どのような場面で使うのか」という適用判断も合わせて解説します。いくつかのパターンについては、Java 17 の実務コードも記載しています。


GoF パターンの全体マップ

まず全体像を把握しておきましょう。

グループ パターン名 一言で言うと
生成 Singleton インスタンスを1つに限定する
生成 Factory Method 生成をサブクラスに委譲する
生成 Abstract Factory 関連オブジェクトをまとめて生成する
生成 Builder 複雑なオブジェクト生成を整理する
生成 Prototype オブジェクトを複製する
構造 Adapter インターフェースを変換する
構造 Bridge 抽象と実装を分離する
構造 Composite 木構造を再帰的に扱う
構造 Decorator 機能を動的に追加する
構造 Facade 複雑なサブシステムを簡潔に使う
構造 Flyweight オブジェクト共有でメモリを節約する
構造 Proxy アクセスを代理・制御する
振る舞い Chain of Responsibility 処理を連鎖させる
振る舞い Command 操作をオブジェクト化する
振る舞い Interpreter 文法規則をクラスで表現する
振る舞い Iterator コレクションを順に走査する
振る舞い Mediator オブジェクト間通信を仲介する
振る舞い Memento 状態を保存・復元する
振る舞い Observer 状態変化を通知する
振る舞い State 状態ごとに振る舞いを切り替える
振る舞い Strategy アルゴリズムを切り替える
振る舞い Template Method 処理の骨格を定義する
振る舞い Visitor 構造と処理を分離する

生成パターン(Creational)

オブジェクトの生成方法にまつわるパターン群です。「どのクラスのインスタンスをどう作るか」を柔軟に制御したいときに使います。

1. Singleton — インスタンスを1つに限定する

どんな場面で使う?
設定情報・DBコネクションプール・ロガーなど、アプリ全体で1つだけ存在すべきオブジェクトに使います。

実装の3方式と選び方

// ① Eager(最もシンプル。クラスロード時に即生成)
public class Config {
    private static final Config INSTANCE = new Config();
    private Config() {}
    public static Config getInstance() { return INSTANCE; }
}
 
// ② Holder(遅延初期化+スレッドセーフ。実務でよく使われる)
public class Config {
    private Config() {}
    private static class Holder {
        static final Config INSTANCE = new Config();
    }
    public static Config getInstance() { return Holder.INSTANCE; }
}
 
// ③ Enum(最も壊れにくい。シリアライズ・リフレクション耐性あり)
public enum Config {
    INSTANCE;
    public String getEnv() { return System.getenv("APP_ENV"); }
}

選び方のポイント

  • 迷ったら Holder方式(遅延初期化かつスレッドセーフ)
  • シリアライズ・リフレクション耐性が必要なら Enum方式
  • Java 8以前の保守案件では Eager方式

詳細記事 → Java Singleton パターンの安全な実装方法と3方式の比較


2. Factory Method — 生成をサブクラスに委譲する

どんな場面で使う?
生成するオブジェクトの種類を呼び出し側から切り離したいとき。DBドライバの切り替えやレポート出力形式の差し替えなどに有効です。「何を作るか」は親クラスが決め、「どう作るか」はサブクラスに任せます。

// 抽象クラス(骨格)
public abstract class ReportCreator {
    public void export() {
        Report report = createReport(); // ← サブクラスに委譲
        report.generate();
    }
    protected abstract Report createReport(); // Factory Method
}
 
// 具象クラス(実装)
public class PdfReportCreator extends ReportCreator {
    @Override
    protected Report createReport() { return new PdfReport(); }
}
 
public class CsvReportCreator extends ReportCreator {
    @Override
    protected Report createReport() { return new CsvReport(); }
}

呼び出し側は ReportCreator だけを知っていればよく、PDF/CSV の切り替えは外から注入できます。

詳細記事 → Java Factory Method パターンで生成を委譲する


3. Abstract Factory — 関連オブジェクトをまとめて生成する

どんな場面で使う?
DB切り替え(MySQL ↔ PostgreSQL)やUIテーマ切り替えのように、関連する複数のオブジェクトをセットでまるごと差し替えたいとき。Factory Method が「1種類の生成」なのに対し、Abstract Factory は「関連する複数種類の生成」をセットで管理します。

// ファクトリーインターフェース
public interface DbFactory {
    Connection createConnection();
    QueryBuilder createQueryBuilder();
}
 
// MySQL用ファクトリー(Connection と QueryBuilder を MySQL版でそろえる)
public class MySqlFactory implements DbFactory {
    @Override public Connection createConnection() { return new MySqlConnection(); }
    @Override public QueryBuilder createQueryBuilder() { return new MySqlQueryBuilder(); }
}
 
// PostgreSQL用ファクトリー(同様に PostgreSQL版でそろえる)
public class PostgreSqlFactory implements DbFactory {
    @Override public Connection createConnection() { return new PostgreSqlConnection(); }
    @Override public QueryBuilder createQueryBuilder() { return new PostgreSqlQueryBuilder(); }
}

詳細記事 → Java Abstract Factory で関連オブジェクトをまとめて生成する


4. Builder — 複雑なオブジェクト生成を整理する

どんな場面で使う?
引数が多いコンストラクタを整理したいとき。必須・任意パラメータの区別が明確になり、不変オブジェクトも作りやすくなります。new MailMessage("to", "subject", null, null, true, false) のような読みにくいコードを解消できます。

public class MailMessage {
    private final String to;
    private final String subject;
    private final String body;
    private final String cc; // 任意
 
    private MailMessage(Builder b) {
        this.to = b.to;
        this.subject = b.subject;
        this.body = b.body;
        this.cc = b.cc;
    }
 
    public static class Builder {
        private final String to;      // 必須
        private final String subject; // 必須
        private String body = "";
        private String cc = null;     // 任意
 
        public Builder(String to, String subject) {
            this.to = to;
            this.subject = subject;
        }
        public Builder body(String body) { this.body = body; return this; }
        public Builder cc(String cc) { this.cc = cc; return this; }
        public MailMessage build() { return new MailMessage(this); }
    }
}
 
// 使用例:何を設定しているか一目でわかる
MailMessage msg = new MailMessage.Builder("to@example.com", "件名")
    .body("本文")
    .cc("cc@example.com")
    .build();

詳細記事 → Java Builder パターンで複雑なオブジェクト生成を整理する


5. Prototype — オブジェクトを複製する

どんな場面で使う?
生成コストが高いオブジェクトや、設定済みのオブジェクトをベースにバリエーションを作りたいとき。Javaでは Cloneable よりもコピーコンストラクタや record のほうが安全で実務的です。

public class UserProfile {
    private final String name;
    private final String role;
 
    public UserProfile(String name, String role) {
        this.name = name;
        this.role = role;
    }
 
    // コピーコンストラクタ(ディープコピーも明示的に書ける)
    public UserProfile(UserProfile original) {
        this.name = original.name;
        this.role = original.role;
    }
}
 
// テンプレートから複製してカスタマイズ
UserProfile template = new UserProfile("Template", "viewer");
UserProfile admin = new UserProfile(template); // 複製してから変更

詳細記事 → Java Prototype パターンでオブジェクトを複製する


構造パターン(Structural)

クラスやオブジェクトの組み合わせ方・つなぎ方に関するパターン群です。既存のクラスを変更せずに柔軟な構造を作るときに役立ちます。

6. Adapter — インターフェースを変換する

どんな場面で使う?
変更できないレガシークラスやサードパーティのクラスを、自分のシステムのインターフェースに合わせたいとき。「形が合わないものを変換器でつなぐ」イメージです。

// 既存クラス(変更不可)
public class LegacyPayment {
    public void executeCharge(int yen) { /* 古い決済処理 */ }
}
 
// 新システムのインターフェース
public interface Payment {
    void pay(int amount);
}
 
// アダプター(橋渡し役)
public class LegacyPaymentAdapter implements Payment {
    private final LegacyPayment legacy = new LegacyPayment();
 
    @Override
    public void pay(int amount) {
        legacy.executeCharge(amount); // メソッド名・シグネチャを変換
    }
}

詳細記事 → Java Adapter パターンで既存クラスのインターフェースを変換する


7. Bridge — 抽象と実装を分離する

どんな場面で使う?
通知の種類(緊急・定期)と送信手段(メール・SMS)のように、2軸が独立して拡張するケース。継承でやろうとすると「緊急×メール」「緊急×SMS」「定期×メール」…とクラスが爆発します。Bridgeは継承ではなく委譲でこれを解決します。

詳細記事 → Java Bridge パターンで抽象と実装を分離する設計と実装


8. Composite — 木構造を再帰的に扱う

どんな場面で使う?
ファイルシステム(ファイル+フォルダ)・組織階層・メニュー構造のように、「個」と「グループ」を同一インターフェースで透過的に扱いたいとき。再帰処理と相性が良いパターンです。

詳細記事 → Java Composite パターンで木構造を再帰的に扱う


9. Decorator — 機能を動的に追加する

どんな場面で使う?
既存クラスを変更せずに機能を後付けしたいとき。ログ出力・バリデーション・キャッシュのラップに有効です。継承と違い、実行時に組み合わせを変えられます。

public interface TextProcessor {
    String process(String text);
}
 
// 基本実装(何もしない)
public class PlainText implements TextProcessor {
    @Override public String process(String text) { return text; }
}
 
// デコレーター:前後に処理を追加するだけ
public class TrimDecorator implements TextProcessor {
    private final TextProcessor wrapped;
    public TrimDecorator(TextProcessor wrapped) { this.wrapped = wrapped; }
 
    @Override
    public String process(String text) {
        return wrapped.process(text.trim()); // trim してから委譲
    }
}
 
public class UpperCaseDecorator implements TextProcessor {
    private final TextProcessor wrapped;
    public UpperCaseDecorator(TextProcessor wrapped) { this.wrapped = wrapped; }
 
    @Override
    public String process(String text) {
        return wrapped.process(text).toUpperCase();
    }
}
 
// 重ねがけ:trim → uppercase の順に適用
TextProcessor processor = new UpperCaseDecorator(new TrimDecorator(new PlainText()));
processor.process("  hello  "); // → "HELLO"

詳細記事 → Java Decorator パターンで機能を動的に追加する


10. Facade — 複雑なサブシステムを簡潔に使う

どんな場面で使う?
SMTP・テンプレートエンジン・監査ログなど、複数のサブシステムを組み合わせた処理を、呼び出し側からシンプルに扱えるよう窓口(Facade)にまとめたいとき。依存を1か所に集約できるので、後からサブシステムを差し替えやすくなります。

詳細記事 → Java Facade パターンで複雑なサブシステムを簡潔に使う


11. Flyweight — オブジェクト共有でメモリを節約する

どんな場面で使う?
同じ属性を持つオブジェクトが大量に生成される場面でキャッシュして共有します。Integer.valueOf(-128〜127) のキャッシュや String.intern() が代表例。ゲームのタイルやフォントグリフの描画などでも使われます。

詳細記事 → Java Flyweight パターンでオブジェクト共有によりメモリを節約する


12. Proxy — アクセスを代理・制御する

どんな場面で使う?
遅延初期化(重い処理を必要になるまで遅らせる)・アクセス制御・ロギング・リモートアクセスの透過化。Spring AOP の根幹にもなっているパターンで、知らずに使っていることが多いパターンの代表です。

詳細記事 → Java Proxy パターンで遅延初期化とアクセス制御を実装する


振る舞いパターン(Behavioral)

オブジェクト間の責任の分担・通信・アルゴリズムの切り替えに関するパターン群です。「誰が何をするか」を整理したいときに使います。

13. Chain of Responsibility — 処理を連鎖させる

どんな場面で使う?
ログフィルタリング・バリデーション・権限チェックのように、複数の処理を順に試み、どこかで処理されたら終了というフローに使います。処理の追加・順序の変更がハンドラーを差し替えるだけで済みます。

詳細記事 → Java Chain of Responsibility 実装と適用判断


14. Command — 操作をオブジェクト化する

どんな場面で使う?
Undo/Redo・操作履歴・キュー処理。操作をオブジェクトとして扱うことで、実行・取り消し・再実行・ログ保存がすべて統一インターフェースで扱えます。

public interface Command {
    void execute();
    void undo();
}
 
public class InsertTextCommand implements Command {
    private final StringBuilder buffer;
    private final String text;
 
    public InsertTextCommand(StringBuilder buffer, String text) {
        this.buffer = buffer;
        this.text = text;
    }
 
    @Override public void execute() { buffer.append(text); }
    @Override public void undo() {
        buffer.delete(buffer.length() - text.length(), buffer.length());
    }
}
 
// 使用例
var buffer = new StringBuilder("Hello");
var cmd = new InsertTextCommand(buffer, " World");
cmd.execute(); // "Hello World"
cmd.undo();    // "Hello" に戻る

詳細記事 → Java Command パターンで Undo/Redo を実装する


15. Interpreter — 文法規則をクラスで表現する

どんな場面で使う?
簡易な式評価・独自DSL・クエリパーサーの実装。文法の各規則をクラスで表現し、ツリー構造で評価します。四則演算の式評価が典型例です。ただし文法が複雑になるとクラス数が増えすぎるため、本格的なパーサーには別のアプローチが適しています。

詳細記事 → Java Interpreter パターンで式評価を実装する


16. Iterator — コレクションを順に走査する

どんな場面で使う?
カスタムコレクションの走査。Iterable / Iterator を実装することで for-each 構文が使えるようになり、内部構造を隠したまま走査ロジックを提供できます。Java の ArrayListLinkedList も当然このパターンを実装しています。

詳細記事 → Java Iterator パターンとカスタムコレクション実装


17. Mediator — オブジェクト間通信を仲介する

どんな場面で使う?
チャットルーム・GUIコンポーネント間の連携のように、複数のオブジェクトが直接参照し合うと依存関係がスパゲッティ化するケース。全員が仲介者(Mediator)だけを知っていればよくなり、結合度が下がります。

詳細記事 → Java Mediator パターンでオブジェクト間通信を整理する


18. Memento — 状態を保存・復元する

どんな場面で使う?
テキストエディタのUndoや、ゲームのセーブ機能のように、オブジェクトの状態をスナップショットとして保存・復元したいとき。Command パターンと組み合わせてUndoスタックを実装することが多いです。

詳細記事 → Java Memento パターンで状態の保存と復元を実装する


19. Observer — 状態変化を通知する

どんな場面で使う?
イベント通知・MVCのモデル変更通知・リアクティブプログラミングの基礎。PropertyChangeListener や Spring の ApplicationEvent もこのパターンです。「変化があったら知らせてほしい」という関係を表現します。

public interface Observer {
    void onUpdate(String event);
}
 
public class EventBus {
    private final List<Observer> observers = new ArrayList<>();
 
    public void subscribe(Observer o) { observers.add(o); }
    public void unsubscribe(Observer o) { observers.remove(o); }
 
    public void publish(String event) {
        observers.forEach(o -> o.onUpdate(event));
    }
}
 
// 使用例
var bus = new EventBus();
bus.subscribe(event -> System.out.println("受信: " + event));
bus.publish("ORDER_COMPLETED"); // → "受信: ORDER_COMPLETED"

詳細記事 → Java Observer パターンでイベント通知を実装する


20. State — 状態ごとに振る舞いを切り替える

どんな場面で使う?
自動販売機・ワークフロー・注文ステータス管理のように、状態によって振る舞いが変わるオブジェクトに使います。状態が増えるたびに if/switch が膨らむのを防ぎ、状態ごとのロジックをクラスに分離できます。

詳細記事 → Java State パターンで状態遷移を安全に実装する設計手法


21. Strategy — アルゴリズムを切り替える

どんな場面で使う?
ソートアルゴリズムの差し替え・決済方法の切り替えなど、実行時にアルゴリズムを差し替えたいとき。Java の Comparator はStrategyパターンの好例で、@FunctionalInterface として定義されているためラムダで簡潔に書けます。

@FunctionalInterface
public interface SortStrategy {
    void sort(int[] array);
}
 
public class Sorter {
    private SortStrategy strategy;
 
    public Sorter(SortStrategy strategy) { this.strategy = strategy; }
    public void setStrategy(SortStrategy strategy) { this.strategy = strategy; }
    public void sort(int[] array) { strategy.sort(array); }
}
 
// ラムダで渡せる(戦略の切り替えが1行で済む)
var sorter = new Sorter(Arrays::sort);
sorter.sort(new int[]{3, 1, 4, 1, 5});
 
// 実行時に戦略を差し替え
sorter.setStrategy(array -> { /* バブルソートに切り替え */ });

詳細記事 → Java Strategy パターンでアルゴリズムを切り替える


22. Template Method — 処理の骨格を定義する

どんな場面で使う?
バッチ処理・レポート生成のように、処理の流れ(読む→処理→書く)は共通だが一部のステップだけ差し替えたいとき。「アルゴリズムの骨格を親クラスに固定し、詳細をサブクラスに委ねる」のがポイントです。

public abstract class BatchJob {
    // 骨格を final で固定(順序を変えられないようにする)
    public final void run() {
        readData();
        processData();
        writeResult();
    }
 
    protected abstract void readData();
    protected abstract void processData();
    protected abstract void writeResult();
}
 
// サービスAのバッチ:readData・processData・writeResult だけ実装すればよい
public class SalesAggregationJob extends BatchJob {
    @Override protected void readData() { /* DBから売上取得 */ }
    @Override protected void processData() { /* 集計処理 */ }
    @Override protected void writeResult() { /* CSVに出力 */ }
}

詳細記事 → Java Template Method パターンでバッチ処理を整理する


23. Visitor — 構造と処理を分離する

どんな場面で使う?
ファイルシステム走査・ASTの解析など、データ構造に手を加えずに新しい処理(例:サイズ計算、権限チェック)を追加したいとき。「構造はそのままに、操作だけ追加する」ことができます。ただし構造側のクラスを変更しにくくなるトレードオフがあります。

詳細記事 → Java Visitor パターンで構造と処理を分離する設計と実装


パターン選択チートシート

「どのパターンを使えばいい?」と迷ったときの判断フローです。

オブジェクトの「生成」に関する問題
├── インスタンスを1つに限定したい       → Singleton
├── 生成処理をサブクラスに任せたい      → Factory Method
├── 関連オブジェクトをセットで差し替えたい → Abstract Factory
├── 引数が多くて生成が複雑             → Builder
└── 既存オブジェクトを複製したい        → Prototype
 
「構造」に関する問題
├── インターフェースが合わない          → Adapter
├── 2軸で独立して拡張したい           → Bridge
├── 木構造を再帰的に扱いたい           → Composite
├── 機能を後から追加したい             → Decorator
├── 複雑な処理を1つの窓口に隠したい    → Facade
├── 大量オブジェクトのメモリを節約したい → Flyweight
└── アクセスを制御・遅延したい         → Proxy
 
「振る舞い」に関する問題
├── アルゴリズムを切り替えたい         → Strategy
├── 状態によって振る舞いが変わる        → State
├── 操作を取り消したい                → Command(+ Memento)
├── 変化を通知したい                  → Observer
├── 処理を順番に試したい              → Chain of Responsibility
├── 処理の流れは共通、一部だけ変えたい  → Template Method
├── 構造に手を加えず処理を追加したい    → Visitor
├── 複数オブジェクトの通信を整理したい  → Mediator
├── コレクションを走査したい           → Iterator
└── 状態のスナップショットを保存したい  → Memento

まとめ

GoFの23パターンは「名前を知っている」だけでなく、「どの場面で使うか判断できる」 ことが重要です。

最初から全部覚えようとせず、まず Singleton・Strategy・Observer・Template Method あたりから実務コードで使ってみるのがおすすめです。「あ、これパターン名があるんだ」という気づきが積み重なると、設計の議論で共通言語として使えるようになります。

各パターンのより詳しい解説・コード例・FAQ・バージョン差分(Java 8/17/21)は dev-craft.dev にまとめています。ぜひ合わせて参照してください。


参考

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?