8.

Java デザインパターン完全ガイド — GoF 23 パターン

編集
この記事の要点
  • GoF (Gang of Four) 23 パターン生成 / 構造 / 振舞いの 3 系統に分類
  • 生成系: Singleton / Factory Method / Abstract Factory / Builder / Prototype
  • 構造系: Adapter / Decorator / Facade / Composite / Proxy / Bridge / Flyweight
  • 振舞い系: Observer / Strategy / Command / Iterator / Template Method / State / Chain of Responsibility / Visitor / Mediator / Memento / Interpreter
  • Spring は DI / Singleton / Proxy / Template Method (JdbcTemplate) 等を内部で活用。現代では関数型 interface + ラムダが Strategy / Command の代替に

23 パターン分類

系統パターン名用途
生成 CreationalSingletonインスタンス唯一性
Factory Methodサブクラスに生成委譲
Abstract Factory関連オブジェクト群を一括生成
Builder複雑なオブジェクトを段階的に組立
Prototype既存インスタンスをクローン
構造 StructuralAdapter非互換 IF を変換
Bridge抽象と実装を分離
Composite木構造を一様に扱う
Decorator機能を動的に追加
Facadeサブシステムへの統一窓口
Flyweight多数オブジェクトの共有
Proxy代理オブジェクトでアクセス制御
振舞 BehavioralChain of Responsibility要求を順に処理
Command処理をオブジェクト化
Interpreter言語の文法を表現
Iterator集約への走査手段
Mediatorオブジェクト間調停
Memento状態のスナップショット
Observer状態変化を通知
State状態ごとの振舞い切替
Strategyアルゴリズム差替
Template Method骨格を親クラスで定義
Visitor構造への操作追加

Singleton

// enum で実装するのが最も安全(Joshua Bloch 推奨)
public enum Config {
    INSTANCE;
    private String name = "default";
    public String getName() { return name; }
}

Config.INSTANCE.getName();

// 古典: private constructor + static field
public final class Logger {
    private static final Logger INSTANCE = new Logger();
    private Logger() {}
    public static Logger getInstance() { return INSTANCE; }
}

Factory Method

interface Shape { void draw(); }
class Circle implements Shape { public void draw() {} }
class Square implements Shape { public void draw() {} }

class ShapeFactory {
    public static Shape create(String type) {
        return switch (type) {
            case "circle" -> new Circle();
            case "square" -> new Square();
            default       -> throw new IllegalArgumentException(type);
        };
    }
}

Shape s = ShapeFactory.create("circle");

Builder

public class User {
    private final String name;
    private final int age;
    private final String email;

    private User(Builder b) {
        this.name = b.name; this.age = b.age; this.email = b.email;
    }
    public static Builder builder() { return new Builder(); }

    public static class Builder {
        private String name; private int age; private String email;
        public Builder name(String n)  { this.name  = n; return this; }
        public Builder age(int a)      { this.age   = a; return this; }
        public Builder email(String e) { this.email = e; return this; }
        public User build() { return new User(this); }
    }
}

User u = User.builder().name("Taro").age(30).email("t@x").build();

Adapter

// 旧 IF
interface OldPrinter { void printText(String s); }

// 新 IF(呼び出し側はこちらを使いたい)
interface NewPrinter { void print(Object o); }

// アダプタで橋渡し
class PrinterAdapter implements NewPrinter {
    private final OldPrinter old;
    public PrinterAdapter(OldPrinter o) { this.old = o; }
    @Override public void print(Object o) { old.printText(o.toString()); }
}

Decorator

interface Coffee {
    int cost();
    String description();
}

class Espresso implements Coffee {
    public int cost() { return 300; }
    public String description() { return "Espresso"; }
}

// 装飾の基底
abstract class CoffeeDecorator implements Coffee {
    protected final Coffee base;
    protected CoffeeDecorator(Coffee b) { this.base = b; }
}

class Milk extends CoffeeDecorator {
    public Milk(Coffee b) { super(b); }
    public int cost() { return base.cost() + 50; }
    public String description() { return base.description() + " + Milk"; }
}

Coffee c = new Milk(new Espresso());
c.cost();         // 350
c.description();  // "Espresso + Milk"

// 実は java.io はこのパターンだらけ
// new BufferedReader(new InputStreamReader(new FileInputStream(...)))

Observer

import java.util.*;

interface Listener { void onEvent(String msg); }

class Publisher {
    private final List listeners = new ArrayList<>();
    public void subscribe(Listener l) { listeners.add(l); }
    public void publish(String msg) {
        for (Listener l : listeners) l.onEvent(msg);
    }
}

Publisher p = new Publisher();
p.subscribe(msg -> System.out.println("A: " + msg));
p.subscribe(msg -> System.out.println("B: " + msg));
p.publish("hello");

Strategy

// Java 8+ の関数型 interface で簡潔に
import java.util.*;
import java.util.function.*;

class Sorter {
    public static  void sort(List list, Comparator strategy) {
        list.sort(strategy);
    }
}

List list = new ArrayList<>(List.of("banana", "apple", "cherry"));
Sorter.sort(list, Comparator.naturalOrder());            // 昇順
Sorter.sort(list, Comparator.reverseOrder());            // 降順
Sorter.sort(list, Comparator.comparingInt(String::length)); // 長さ順

Template Method

abstract class ReportBuilder {
    // テンプレート(手順を固定)
    public final String build() {
        return header() + body() + footer();
    }
    protected String header() { return "=== Report ===\n"; }
    protected abstract String body();   // サブクラスで実装
    protected String footer() { return "\n=== End ===\n"; }
}

class SalesReport extends ReportBuilder {
    @Override protected String body() { return "Sales: 1,234,567 JPY"; }
}

// Spring JdbcTemplate / RestTemplate もこのパターン

Proxy

interface Service { String fetch(int id); }

class RealService implements Service {
    public String fetch(int id) { /* 重い処理 */ return "data:" + id; }
}

class CachingProxy implements Service {
    private final Service real;
    private final Map cache = new HashMap<>();

    public CachingProxy(Service r) { this.real = r; }

    public String fetch(int id) {
        return cache.computeIfAbsent(id, real::fetch);
    }
}

// Spring AOP / トランザクション境界もこのパターン

現代の Java での代替手段

古典パターン現代の代替
Strategy関数型 interface (Function / Predicate) + ラムダ
CommandRunnable / Consumer + ラムダ
SingletonSpring の @Component(DI コンテナで管理)
ObserverSpring の @EventListener / リアクティブストリーム
FactoryDI コンテナの bean 定義
Iterator拡張 for / Stream

FAQ

Q: 全 23 パターンを覚える必要ある?
A: 不要。実務で頻出は Singleton / Factory / Builder / Strategy / Observer / Decorator / Proxy / Template Method の 8 個程度。

Q: パターンを使うと過剰設計にならない?
A: なります。必要になったときに導入するのが鉄則。YAGNI (You Aren't Gonna Need It) 原則。

Q: Spring を使うとどのパターンが自動で適用される?
A: DI による Strategy / Factory、シングルトン Bean、AOP の Proxy、JdbcTemplate / RestTemplate の Template Method など。

編集
Post Share
子ページ
  1. MVC
同階層のページ
  1. プラットホーム
  2. 環境構築
  3. 文法
  4. API
  5. Servlet(サーブレット)
  6. JSP
  7. Applet(アプレット)
  8. デザインパターン
  9. フレームワーク
  10. ライブラリ
  11. Androidアプリケーション
  12. Project Jigsaw
  13. エラー一覧
  14. 日付の加算、減算
  15. 文字列の数字チェック
  16. 改行コードの削除
  17. 先頭と末端の文字の削除
  18. warファイルの中身を確認する方法
  19. nullもしくは空文字の判定
  20. beanの中身を確認する方法
  21. org.apache.log4j.Logger のログ出力で printStackTrace() のエラー内容を出力する方法
  22. Javaのバージョン確認方法