SJ blog
Java
Z

信頼度ランク

S 公式ソース確認済み
A 成功実績多数・失敗例少数
B 賛否両論
C 動作未確認・セキュリティリスク高
Z 個人所感

Javaでよく使うデザインパターン実践ガイド

Singleton、Factory、Builder、Observer、Strategyなど、Javaで頻出するデザインパターンを実例コード付きで解説します。

一言結論

デザインパターンは実装の雛形ではなく設計の共通言語であり、「Strategyで切り替える」「Observerで通知する」と言えるだけで設計議論の質が格段に上がる。

デザインパターンとは

繰り返し現れる設計の問題に対する、名前の付いた解決策のカタログです。「Singletonにする」「Factoryで生成する」と話すだけで設計の意図が伝わります。

Singleton(シングルトン)

インスタンスを1つだけ生成することを保証するパターン。

// スレッドセーフなSingleton(推奨)
public class DatabaseConnection {
    // クラスロード時に初期化される(スレッドセーフ)
    private static final DatabaseConnection INSTANCE = new DatabaseConnection();
    
    private DatabaseConnection() {
        // コンストラクタを private にして外部からの生成を禁止
    }
    
    public static DatabaseConnection getInstance() {
        return INSTANCE;
    }
    
    public void query(String sql) { ... }
}

// 使い方
DatabaseConnection.getInstance().query("SELECT * FROM users");

注意: テストがしにくくなるため、依存性注入(DI)の方が現代では好まれます。

Factory Method(ファクトリメソッド)

オブジェクトの生成をサブクラスに委ねるパターン。

public interface Notification {
    void send(String message);
}

public class EmailNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("Email: " + message);
    }
}

public class SMSNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("SMS: " + message);
    }
}

// ファクトリ
public class NotificationFactory {
    public static Notification create(String type) {
        return switch (type) {
            case "email" -> new EmailNotification();
            case "sms" -> new SMSNotification();
            default -> throw new IllegalArgumentException("Unknown type: " + type);
        };
    }
}

// 使い方
Notification n = NotificationFactory.create("email");
n.send("こんにちは");

Builder(ビルダー)

複雑なオブジェクトの構築を読みやすくするパターン。

public class User {
    private final String name;      // 必須
    private final String email;     // 必須
    private final String phone;     // オプション
    private final int age;          // オプション
    
    private User(Builder builder) {
        this.name = builder.name;
        this.email = builder.email;
        this.phone = builder.phone;
        this.age = builder.age;
    }
    
    public static class Builder {
        private final String name;
        private final String email;
        private String phone;
        private int age;
        
        public Builder(String name, String email) {
            this.name = name;
            this.email = email;
        }
        
        public Builder phone(String phone) {
            this.phone = phone;
            return this;
        }
        
        public Builder age(int age) {
            this.age = age;
            return this;
        }
        
        public User build() {
            return new User(this);
        }
    }
}

// 使い方(Lombok の @Builder でも同様のことができる)
User user = new User.Builder("Alice", "alice@example.com")
    .phone("090-1234-5678")
    .age(30)
    .build();

Observer(オブザーバー)

オブジェクトの状態変化を他のオブジェクトに通知するパターン。

public interface EventListener {
    void onEvent(String event);
}

public class EventBus {
    private final List<EventListener> listeners = new ArrayList<>();
    
    public void subscribe(EventListener listener) {
        listeners.add(listener);
    }
    
    public void publish(String event) {
        listeners.forEach(l -> l.onEvent(event));
    }
}

// 使い方
EventBus bus = new EventBus();
bus.subscribe(event -> System.out.println("Logger: " + event));
bus.subscribe(event -> sendNotification(event));
bus.publish("user.login");

Strategy(ストラテジー)

アルゴリズムをカプセル化して切り替えられるパターン。

@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);
    }
}

// 使い方(Lambda でストラテジーを渡す)
Sorter sorter = new Sorter(array -> Arrays.sort(array));

// ストラテジーを切り替える
sorter.setStrategy(array -> bubbleSort(array));

Decorator(デコレータ)

オブジェクトに動的に機能を追加するパターン。

public interface Coffee {
    String getDescription();
    double getCost();
}

public class SimpleCoffee implements Coffee {
    public String getDescription() { return "Coffee"; }
    public double getCost() { return 100; }
}

public abstract class CoffeeDecorator implements Coffee {
    protected final Coffee coffee;
    public CoffeeDecorator(Coffee coffee) { this.coffee = coffee; }
}

public class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) { super(coffee); }
    public String getDescription() { return coffee.getDescription() + ", Milk"; }
    public double getCost() { return coffee.getCost() + 50; }
}

public class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) { super(coffee); }
    public String getDescription() { return coffee.getDescription() + ", Sugar"; }
    public double getCost() { return coffee.getCost() + 30; }
}

// 使い方
Coffee coffee = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
System.out.println(coffee.getDescription());  // Coffee, Milk, Sugar
System.out.println(coffee.getCost());         // 180

Template Method(テンプレートメソッド)

処理の骨格を定義し、詳細をサブクラスに委ねるパターン。

public abstract class ReportGenerator {
    // テンプレートメソッド(final で変更不可)
    public final void generate() {
        fetchData();
        String data = processData();
        format(data);
        output(data);
    }
    
    protected abstract void fetchData();
    protected abstract String processData();
    
    protected void format(String data) {
        // デフォルト実装(オーバーライド可)
    }
    
    protected void output(String data) {
        System.out.println(data);
    }
}

まとめ

パターン解決する問題
Singletonインスタンスを1つに制限
Factory Method生成ロジックをカプセル化
Builder複雑なオブジェクトの生成を読みやすく
Observer状態変化の通知を疎結合に
Strategyアルゴリズムの切り替えを柔軟に
Decorator機能の動的な追加
Template Method処理の骨格と詳細を分離

パターンを使う目的は「コードを複雑にすること」ではなく「変更に強いコードにすること」です。シンプルな解決策で十分な場合は無理にパターンを適用しなくてよいです。