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 | 処理の骨格と詳細を分離 |
パターンを使う目的は「コードを複雑にすること」ではなく「変更に強いコードにすること」です。シンプルな解決策で十分な場合は無理にパターンを適用しなくてよいです。