Javaにおける代表的なデザインパターンの実装と適用シナリオ

Javaアプリケーションの設計品質を高めるため、再利用性・拡張性・保守性を意識したオブジェクト指向の設計原則が不可欠です。その中でも、実践的に最も頻繁に活用されるデザインパターンを、目的別に分類し、各パターンの本質的な役割と安全な実装手法を解説します。

インスタンス生成を制御する:生成系パターン

Singleton(シングルトン)— 一意のグローバルインスタンスの保証

シングルトンは、クラスのインスタンスがアプリケーション全体で唯一であることを保証するパターンです。実装方式によってスレッド安全性と初期化タイミングが大きく異なります。

静的初期化による即時インスタンス化(Eager Initialization):
クラスロード時に必ずインスタンスが生成されるため、確実なスレッド安全性を提供しますが、使用されない場合でもリソースを消費します。

public final class Configuration {
    private static final Configuration INSTANCE = new Configuration();

    private Configuration() {}

    public static Configuration getInstance() {
        return INSTANCE;
    }
}

遅延初期化+二重チェックロック(Double-Checked Locking):
volatile修飾子を用いることで、JVMの命令順序の最適化による不正な参照を防ぎ、効率的かつ安全な遅延初期化を実現します。

public class ResourceManager {
    private static volatile ResourceManager instance;

    private ResourceManager() {}

    public static ResourceManager getInstance() {
        if (instance == null) {
            synchronized (ResourceManager.class) {
                if (instance == null) {
                    instance = new ResourceManager();
                }
            }
        }
        return instance;
    }
}

静的内部クラスによる遅延ロード(Initialization-on-Demand Holder Idiom):
JVM仕様に準拠した自然なスレッドセーフな遅延初期化。外部クラスのロードとは独立して、初回アクセス時にのみ内部クラスが初期化されます。

public class DatabaseConnection {
    private DatabaseConnection() {}

    private static class ConnectionHolder {
        static final DatabaseConnection INSTANCE = new DatabaseConnection();
    }

    public static DatabaseConnection getInstance() {
        return ConnectionHolder.INSTANCE;
    }
}

Factory(ファクトリ)— オブジェクト生成の抽象化

クライアントが具体的なクラス名を知らずにインスタンスを取得できるよう、生成ロジックをカプセル化します。開閉原則への適合度で3種類に分けられます。

シンプルファクトリ(非推奨/学習用途):
条件分岐による生成処理を1つのクラスに集約。新規製品追加時にファクトリクラスの修正が必要となり、開閉原則に反します。

public class PaymentProcessorFactory {
    public static PaymentProcessor create(String type) {
        return switch (type.toLowerCase()) {
            case "credit" -> new CreditCardProcessor();
            case "paypal" -> new PayPalProcessor();
            default -> throw new IllegalArgumentException("Unsupported payment type: " + type);
        };
    }
}

ファクトリメソッド(Factory Method):
抽象ファクトリインタフェースを定義し、各サブクラスが自身の製品を生成します。新規製品追加時に既存コードを変更せず、新しいファクトリクラスを追加するだけで済みます。

interface NotificationService {
    void send(String message);
}

interface ServiceFactory {
    NotificationService createService();
}

class EmailService implements NotificationService {
    public void send(String msg) { System.out.println("Email sent: " + msg); }
}

class SmsService implements NotificationService {
    public void send(String msg) { System.out.println("SMS sent: " + msg); }
}

class EmailFactory implements ServiceFactory {
    public NotificationService createService() { return new EmailService(); }
}

class SmsFactory implements ServiceFactory {
    public NotificationService createService() { return new SmsService(); }
}

抽象ファクトリ(Abstract Factory):
関連する一連の製品群(例:テーマごとのUIコンポーネント)をまとめて提供するファクトリ。異なるプラットフォームやスタイルに対応する際の柔軟性を高めます。

interface Button { void render(); }
interface Checkbox { void render(); }

interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

class WindowsFactory implements GUIFactory {
    public Button createButton() { return new WindowsButton(); }
    public Checkbox createCheckbox() { return new WindowsCheckbox(); }
}

class MacOSFactory implements GUIFactory {
    public Button createButton() { return new MacOSButton(); }
    public Checkbox createCheckbox() { return new MacOSCheckbox(); }
}

Builder(ビルダー)— 複雑なオブジェクト構築の可読性向上

多数のオプションを持つオブジェクト(例:ネットワーク接続設定、ドメインエンティティ)を、明示的かつ段階的に構築するためのパターンです。フラグ引数やオプショナルパラメータの乱用を防ぎます。

public class ApiRequest {
    private final String endpoint;
    private final int timeoutMs;
    private final boolean useCompression;
    private final String authToken;

    private ApiRequest(Builder builder) {
        this.endpoint = builder.endpoint;
        this.timeoutMs = builder.timeoutMs;
        this.useCompression = builder.useCompression;
        this.authToken = builder.authToken;
    }

    public static class Builder {
        private String endpoint;
        private int timeoutMs = 5000;
        private boolean useCompression = false;
        private String authToken;

        public Builder endpoint(String url) { this.endpoint = url; return this; }
        public Builder timeout(int ms) { this.timeoutMs = ms; return this; }
        public Builder compress() { this.useCompression = true; return this; }
        public Builder auth(String token) { this.authToken = token; return this; }

        public ApiRequest build() {
            if (endpoint == null) throw new IllegalStateException("Endpoint is required");
            return new ApiRequest(this);
        }
    }
}

// 使用例
ApiRequest req = new ApiRequest.Builder()
    .endpoint("https://api.example.com/data")
    .timeout(10_000)
    .compress()
    .auth("Bearer xyz123")
    .build();

構造を柔軟に拡張する:構造系パターン

Proxy(プロキシ)— アクセス制御と振る舞いの付与

対象オブジェクトへのアクセスを制御・監視・遅延化するための中間層として機能します。同一インタフェースを実装することで、クライアントは差異を意識せずに利用できます。

interface Image {
    void display();
}

class RealImage implements Image {
    private final String filename;

    RealImage(String file) {
        this.filename = file;
        loadFromDisk();
    }

    private void loadFromDisk() {
        System.out.println("Loading image: " + filename);
    }

    public void display() {
        System.out.println("Displaying: " + filename);
    }
}

class ImageProxy implements Image {
    private RealImage realImage;
    private final String filename;

    ImageProxy(String file) { this.filename = file; }

    public void display() {
        if (realImage == null) {
            realImage = new RealImage(filename);
        }
        realImage.display();
    }
}

Decorator(デコレータ)— 機能の動的付与

既存のオブジェクトに新たな振る舞いを追加する際に、継承ではなく「ラップ」による合成を採用します。開閉原則と単一責任の原則を両立させます。

interface Stream {
    void write(byte[] data);
}

class FileStream implements Stream {
    public void write(byte[] data) {
        System.out.println("Writing " + data.length + " bytes to file");
    }
}

abstract class StreamDecorator implements Stream {
    protected final Stream decorated;

    StreamDecorator(Stream stream) {
        this.decorated = stream;
    }

    public void write(byte[] data) {
        decorated.write(data);
    }
}

class CompressedStream extends StreamDecorator {
    CompressedStream(Stream stream) { super(stream); }

    @Override
    public void write(byte[] data) {
        byte[] compressed = compress(data);
        System.out.println("Compressing " + data.length + " → " + compressed.length + " bytes");
        super.write(compressed);
    }

    private byte[] compress(byte[] input) {
        return new byte[(int) Math.ceil(input.length * 0.7)];
    }
}

class EncryptedStream extends StreamDecorator {
    EncryptedStream(Stream stream) { super(stream); }

    @Override
    public void write(byte[] data) {
        byte[] encrypted = encrypt(data);
        System.out.println("Encrypting " + data.length + " bytes");
        super.write(encrypted);
    }

    private byte[] encrypt(byte[] input) {
        return new byte[input.length];
    }
}

// 組み合わせ可能
Stream secureStream = new EncryptedStream(new CompressedStream(new FileStream()));

振る舞いを整理・連携する:振る舞い系パターン

Template Method(テンプレートメソッド)— アルゴリズムの骨格と具体化の分離

アルゴリズムの基本フロー(テンプレート)を抽象クラスで定義し、サブクラスに具体的なステップを委ねることで、共通ロジックの重複を排除します。

abstract class DataProcessor {
    // テンプレートメソッド:finalでオーバーライド禁止
    public final void execute() {
        validateInput();
        processData();
        generateReport();
        cleanup();
    }

    protected abstract void processData();

    protected void validateInput() {
        System.out.println("Validating input...");
    }

    protected void generateReport() {
        System.out.println("Generating summary report...");
    }

    protected void cleanup() {
        System.out.println("Releasing resources...");
    }
}

class CsvProcessor extends DataProcessor {
    protected void processData() {
        System.out.println("Parsing CSV and transforming records...");
    }
}

class JsonProcessor extends DataProcessor {
    protected void processData() {
        System.out.println("Deserializing JSON and validating schema...");
    }
}

Chain of Responsibility(責任連鎖)— 条件付き処理の分散

リクエストを処理可能なハンドラが連鎖的に受け渡すことで、送信側と受信側の結合度を下げます。各ハンドラは自身の責任範囲外であれば次のハンドラへ委譲します。

abstract class ApprovalHandler {
    protected ApprovalHandler next;

    public void setNext(ApprovalHandler handler) {
        this.next = handler;
    }

    public abstract void approve(Request request);
}

record Request(double amount, String description) {}

class TeamLeadHandler extends ApprovalHandler {
    public void approve(Request req) {
        if (req.amount() <= 5000) {
            System.out.println("Team Lead approved: " + req.description());
        } else if (next != null) {
            next.approve(req);
        }
    }
}

class ManagerHandler extends ApprovalHandler {
    public void approve(Request req) {
        if (req.amount() <= 20000) {
            System.out.println("Manager approved: " + req.description());
        } else if (next != null) {
            next.approve(req);
        }
    }
}

// 利用例
ApprovalHandler chain = new TeamLeadHandler();
chain.setNext(new ManagerHandler());

chain.approve(new Request(3500, "Office supplies"));
chain.approve(new Request(12000, "New laptop")); // Managerが処理

Observer(オブザーバー)— 状態変化の自動通知

あるオブジェクト(被観察者)の状態変化を、登録された複数のオブザーバーが自動的に検知・反応する仕組みです。MVCアーキテクチャやイベント駆動型システムの基盤となります。

import java.util.*;

interface Observable {
    void addObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

interface Observer {
    void onStateChanged();
}

class Sensor implements Observable {
    private final List<Observer> observers = new ArrayList<>();
    private boolean isActive = false;

    public void setActive(boolean active) {
        this.isActive = active;
        notifyObservers();
    }

    public void addObserver(Observer o) { observers.add(o); }
    public void removeObserver(Observer o) { observers.remove(o); }
    public void notifyObservers() {
        observers.forEach(Observer::onStateChanged);
    }
}

class AlertSystem implements Observer {
    public void onStateChanged() {
        System.out.println("🚨 Alert triggered!");
    }
}

class Dashboard implements Observer {
    public void onStateChanged() {
        System.out.println("📊 Dashboard updated.");
    }
}

// 使用例
Sensor sensor = new Sensor();
sensor.addObserver(new AlertSystem());
sensor.addObserver(new Dashboard());

sensor.setActive(true); // 両方のオブザーバーが呼び出される

タグ: singleton factory builder proxy decorator

5月25日 11:44 投稿