複雑なソフトウェアシステムを構築する際、コードが単に「動く」だけでは不十分です。システムが拡大し要件が変化するにつれて、オブジェクト生成の構造が不適切だったり、結合度が高かったり、ハードコードされた制御フローや静的な継承階層は、脆弱なアーキテクチャにつながります。

本包括ガイドでは、5 つの重要な設計パターン—Abstract FactoryStrategyAdapterObserverDecorator—を、実世界のエンタープライズシナリオ、アンチパターン、トレードオフ、リファクタリング手順とともに解説します。


📊 ソフトウェア設計パターン クイックリファレンス チートシート

パターン カテゴリ 核心意図 実世界のシナリオ 主な利点 主なアーキテクチャ上のトレードオフ
Abstract Factory 生成 具体的なクラスを指定せずに、関連するオブジェクト群を作成する。 多階層のサービスバンドル(例: ベーシック vs. エリート税務パッケージ)。 製品ファミリの不一致を排除し、一貫したオブジェクト生成を強制する。 インターフェースの増加と間接参照のオーバーヘッド。
Strategy 振る舞い 統一インターフェースの背後で、交換可能なアルゴリズムをカプセル化する。 マルチゲートウェイの決済ルーター(Stripe、PayPal、Adyen)。 モノリシックな if-else/switch ブロックを排除し、Open/Closed 原則に準拠する。 単純なアルゴリズムのバリエーションでもクラス/モジュールが増加する。
Adapter 構造 互換性のない 2 つのインターフェース間を変換する。 レガシー KYC SDK を統合するオンボーディングサービス。 外部/レガシー API の変更からドメインロジックを隔離する。 データマッピングのオーバーヘッドと潜在的な漏洩抽象。
Observer 振る舞い 1 対 N の依存関係を定義し、状態変化を購読者にブロードキャストする。 eコマースの注文チェックアウト時のイベント通知。 ドメインコアと下流の副作用を分離する。 実行フローが不明瞭になり、デバッグや追跡が困難になる。
Decorator 構造 クラス構造を変更せずに、オブジェクトに動的に振る舞いを追加する。 不正検知と監査がオプションの決済処理パイプライン。 サブクラスの組み合わせ爆発を回避し、動的な構成を可能にする。 オブジェクト同一性を侵害する(== チェックが失敗する)、順序依存の実行時リスク。

1. Abstract Factory パターン

核心概念

Abstract Factory パターンは、関連または依存するオブジェクト群を作成するためのインターフェースを提供し、その具体的なクラスを指定しません。連携して動作するように設計された一連の製品がまとまりのある単位としてインスタンス化されることを保証し、生成ロジックを消費者コードから分離します。

シナリオ: 階層化された税務サービスパッケージ

あるサービスプロバイダが、2 つのパッケージ化されたサービス階層を提供しているとします。

  1. 税務申告サービス: ベーシック(標準 W-2) vs. エリート(複数州、暗号資産、ストックオプション)。
  2. 税務節約サービス: ベーシック(401k アドバイス) vs. エリート(遺産計画およびオフショア税務シェルター)。

❌ 違反アーキテクチャ: 分散したインスタンス化とファミリ不一致のリスク

ビジネスロジック内で具象製品を直接インスタンス化すると人的ミスが生じます。開発者が誤って BasicTaxFiling サービスと EliteTaxSaving サービスを混在させる可能性があります。

public class TaxServiceOrchestrator {

    public void executeService(String tier) {
        TaxFilingService filing;
        TaxSavingService saving;

        // 人的ミスのリスク: 開発者が BASIC の申告と ELITE の節約を誤って混在させる可能性がある
        if ("BASIC".equalsIgnoreCase(tier)) {
            filing = new BasicTaxFiling();
            saving = new BasicTaxSaving();
        } else if ("ELITE".equalsIgnoreCase(tier)) {
            filing = new EliteTaxFiling();
            saving = new EliteTaxSaving();
        } else {
            throw new IllegalArgumentException("Invalid service tier: " + tier);
        }

        filing.fileTaxes();
        saving.provideAdvice();
    }
}

Enter fullscreen mode Exit fullscreen mode


✅ リファクタリング後のアーキテクチャ: Abstract Factory による強制

構造アーキテクチャ図

graph TD
    subgraph Client Code
        Orchestrator[TaxServiceOrchestrator]
    end

    subgraph Factory Hierarchy
        FactoryInterface[FullServicePackageFactory Interface]
        BasicFactory[BasicPackageFactory]
        EliteFactory[ElitePackageFactory]

        FactoryInterface <|-- BasicFactory
        FactoryInterface <|-- EliteFactory
    end

    subgraph Product Families
        FilingInterface[TaxFilingService]
        SavingInterface[TaxSavingService]

        BasicFiling[BasicTaxFiling]
        EliteFiling[EliteTaxFiling]
        BasicSaving[BasicTaxSaving]
        EliteSaving[EliteTaxSaving]

        FilingInterface <|-- BasicFiling
        FilingInterface <|-- EliteFiling
        SavingInterface <|-- BasicSaving
        SavingInterface <|-- EliteSaving
    end

    Orchestrator --> FactoryInterface
    BasicFactory ..> BasicFiling
    BasicFactory ..> BasicSaving
    EliteFactory ..> EliteFiling
    EliteFactory ..> EliteSaving

Enter fullscreen mode Exit fullscreen mode

コード実装

// 1. 抽象製品 1: 税務申告
public interface TaxFilingService {
    void fileTaxes();
}

// 2. 抽象製品 2: 税務節約
public interface TaxSavingService {
    void provideAdvice();
}

// 3. BASIC 階層の具象製品
public class BasicTaxFiling implements TaxFilingService {
    @Override
    public void fileTaxes() { 
        System.out.println("Filing standard W-2 income taxes."); 
    }
}

public class BasicTaxSaving implements TaxSavingService {
    @Override
    public void provideAdvice() { 
        System.out.println("Providing basic 401(k) contribution advice."); 
    }
}

// 4. ELITE 階層の具象製品
public class EliteTaxFiling implements TaxFilingService {
    @Override
    public void fileTaxes() { 
        System.out.println("Filing multi-state, crypto, and stock-option taxes."); 
    }
}

public class EliteTaxSaving implements TaxSavingService {
    @Override
    public void provideAdvice() { 
        System.out.println("Providing estate planning and offshore tax shelter advice."); 
    }
}

// 5. 抽象ファクトリインターフェース
public interface FullServicePackageFactory {
    TaxFilingService createFilingService();
    TaxSavingService createSavingService();
}

// 6. 具象ファクトリ 1: ベーシックパッケージファミリ
public class BasicPackageFactory implements FullServicePackageFactory {
    @Override
    public TaxFilingService createFilingService() { 
        return new BasicTaxFiling(); 
    }

    @Override
    public TaxSavingService createSavingService() { 
        return new BasicTaxSaving(); 
    }
}

// 7. 具象ファクトリ 2: エリートパッケージファミリ
public class ElitePackageFactory implements FullServicePackageFactory {
    @Override
    public TaxFilingService createFilingService() { 
        return new EliteTaxFiling(); 
    }

    @Override
    public TaxSavingService createSavingService() { 
        return new EliteTaxSaving(); 
    }
}

// 8. クリーンなクライアントコード(ファミリの一貫性が保証され、階層混在のリスクはゼロ)
public class TaxOrchestrator {
    private final TaxFilingService filingService;
    private final TaxSavingService savingService;

    // まとまりのあるファクトリファミリを注入
    public TaxOrchestrator(FullServicePackageFactory packageFactory) {
        this.filingService = packageFactory.createFilingService();
        this.savingService = packageFactory.createSavingService();
    }

    public void runFullService() {
        filingService.fileTaxes();
        savingService.provideAdvice();
    }
}

Enter fullscreen mode Exit fullscreen mode

なぜ必要なのか

  • 具象結合の排除: ビジネスロジックを特定の製品クラスから分離します。
  • 拡張性: クライアントのビジネスコードを変更せずに新しい製品ライン/階層を簡単に追加できます。
  • 製品不一致の防止: ファクトリが作成するインスタンスが厳密に同じ製品ファミリに属することを強制します。
  • SOLID 準拠: Open/Closed 原則および依存性逆転の原則に準拠します。

アーキテクチャ上のトレードオフ

  • 間接参照のオーバーヘッド: 開発者は基盤となる具象インスタンスを調べるためにインターフェースとファクトリの抽象化をたどる必要があります。
  • インターフェースの増加: ファクトリと導入するすべての製品ラインに対して抽象インターフェースを作成する必要があります。

避けるべき、または却下すべき場合

  • 単純で不変のオブジェクト: 標準コンストラクタを持つ単純な POJO、DTO、またはドメインエンティティに対してファクトリを作成するのはオーバーエンジニアリングです。
  • IoC/DI フレームワークの使用: 現代のフレームワーク(例: Spring、NestJS、Wire)はグローバルな IoC コンテナとして機能します。DI コンテナで管理されるシングルトンサービスに対してカスタム Abstract Factory を書くことは冗長です。

2. Strategy パターン

核心概念

Strategy パターンは、アルゴリズムの選択をアルゴリズムの実行から切り離します。複雑な条件分岐エンジンをコアビジネスロジックに埋め込む代わりに、各アルゴリズムのバリエーションを共通のインターフェースの背後にカプセル化します。

シナリオ: マルチゲートウェイ決済ルーター

決済ルーティングシステムは、ユーザーコンテキストや地域に基づいて、異なる決済プロバイダ(StripePayPalAdyen)を通じてトランザクションを処理する必要があります。

❌ 違反アーキテクチャ: 条件分岐のモノリス

public class PaymentProcessor {

    public PaymentResult processPayment(String gateway, BigDecimal amount, PaymentInfo info) {
        if ("STRIPE".equalsIgnoreCase(gateway)) {
            // 50 行の Stripe SDK 呼び出し、ヘッダー設定、署名生成
            System.out.println("Processing via Stripe SDK...");
            return new PaymentResult(true, "ch_stripe_123");
        } else if ("PAYPAL".equalsIgnoreCase(gateway)) {
            // 60 行の PayPal OAuth トークン取得とペイロードマッピング
            System.out.println("Processing via PayPal REST API...");
            return new PaymentResult(true, "pay_paypal_456");
        } else if ("ADYEN".equalsIgnoreCase(gateway)) {
            // 40 行の Adyen HMAC 署名生成とリクエスト構築
            System.out.println("Processing via Adyen API...");
            return new PaymentResult(true, "adyen_tx_789");
        } else {
            throw new IllegalArgumentException("Unsupported payment gateway: " + gateway);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode


✅ リファクタリング後のアーキテクチャ: カプセル化された Strategy レジストリ

graph LR
    Client[PaymentService] --> Registry[PaymentRegistry]
    Registry --> StrategyInterface[PaymentStrategy Interface]
    StrategyInterface <|.. Stripe[StripeStrategy]
    StrategyInterface <|.. PayPal[PayPalStrategy]
    StrategyInterface <|.. Adyen[AdyenStrategy]

Enter fullscreen mode Exit fullscreen mode

コード実装

// 1. 共通 Strategy インターフェース
public interface PaymentStrategy {
    void pay(double amount);
}

// 2. 具象 Strategy A
public class StripeStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        // Stripe API 固有のロジック
        System.out.println("Paid $" + amount + " using Stripe.");
    }
}

// 具象 Strategy B
public class PayPalStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        // PayPal API 固有のロジック
        System.out.println("Paid $" + amount + " using PayPal.");
    }
}

// 3. シンプルな Strategy レジストリ
public class PaymentRegistry {
    private final Map<String, PaymentStrategy> strategies = new HashMap<>();

    public PaymentRegistry() {
        // キーごとに戦略を登録
        strategies.put("STRIPE", new StripeStrategy());
        strategies.put("PAYPAL", new PayPalStrategy());
    }

    public PaymentStrategy get(String type) {
        PaymentStrategy strategy = strategies.get(type.toUpperCase());
        if (strategy == null) {
            throw new IllegalArgumentException("Invalid payment type: " + type);
        }
        return strategy;
    }
}

// 4. クライアントコード
public class PaymentService {
    private final PaymentRegistry registry = new PaymentRegistry();

    public void processPayment(String gatewayType, double amount) {
        // 一致する戦略を取得して実行
        PaymentStrategy strategy = registry.get(gatewayType);
        strategy.pay(amount);
    }

    public static void main(String[] args) {
        PaymentService service = new PaymentService();
        service.processPayment("STRIPE", 100.0); // 出力: Paid $100.0 using Stripe.
        service.processPayment("PAYPAL", 50.0);   // 出力: Paid $50.0 using PayPal.
    }
}

Enter fullscreen mode Exit fullscreen mode

なぜ必要なのか

  • 制御フローの肥大化を排除: 複雑な if-else または switch ブロックをルックアップメカニズムに置き換えます。
  • 分離されたテスト: 個々のアルゴリズムは、周囲のサービスをインスタンス化せずに分離してユニットテストできます。
  • Open/Closed 原則への準拠: 新しい統合(例: NetBankingStrategy)を追加するには、クラスを追加して登録するだけで済みます。

アーキテクチャ上のトレードオフ

  • クラス/モジュールの爆発: 単一の多分岐ファイルと引き換えに、複数の具象戦略クラス、レジストリ、およびインターフェース契約が必要になります。

避けるべき、または却下すべき場合

  • 不変/自明なロジック: バリエーションが単純で変更が稀な場合、標準的な条件分岐文で十分です。
  • 関数型プログラミングのパラダイム: 一級関数を持つ言語(例: TypeScript、Rust、Go、モダン Java)では、戦略のための重いクラス階層を作成することはアンチパターンです。ラムダや高階関数を渡すことが推奨されます。

3. Adapter パターン

核心概念

Adapter パターンは、互換性のないインターフェース間の構造的な翻訳者として機能します。サードパーティ API やレガシーシステムとの間でリクエストを送受信しながら、コアドメイン契約をクリーンに保つことができます。

シナリオ: KYC 検証統合

オンボーディングサービスは、サードパーティの Know-Your-Customer (KYC) 本人確認を必要とし、レガシーな外部ベンダー SDK(LegacyPersonaSdk)を使用します。

❌ 違反アーキテクチャ: 外部契約によるドメインの汚染

// レガシーベンダークラス(変更不可の外部 SDK)
class LegacyPersonaSdk {
    public int executeCheck(String passportNum, String countryIso2) {
        // パスは 200、失敗は 401、エラーは 500 を返す
        System.out.println("Calling Legacy Persona API...");
        return 200;
    }
}

// 違反したビジネスロジックは、ベンダー固有のパラメータとステータスコードで直接汚染される
public class OnboardingService {
    private final LegacyPersonaSdk legacySdk = new LegacyPersonaSdk();

    public boolean verifyUser(String passportNumber, String countryCode) {
        // レガシーメソッドシグネチャとベンダーステータスコードへの直接結合
        int responseCode = legacySdk.executeCheck(passportNumber, countryCode);
        return responseCode == 200;
    }
}

Enter fullscreen mode Exit fullscreen mode


✅ リファクタリング後のアーキテクチャ: ドメインアダプタによる隔離

graph LR
    Client[OnboardingService] --> TargetInterface[KycVerifier Interface]
    Adapter[LegacyPersonaAdapter] ..|> TargetInterface
    Adapter --> Adaptee[LegacyPersonaSdk]

Enter fullscreen mode Exit fullscreen mode

コード実装

// 1. クリーンなアプリケーションターゲット契約
public interface KycVerifier {
    KycResult verifyUser(UserIdentity identity);
}

// ドメインモデル
record UserIdentity(String passportNumber, String countryCode) {}
record KycResult(boolean status, String referenceId) {}

// 2. 変更不可の外部 API / Adaptee
class LegacyPersonaSdk {
    public int executeCheck(String passportNum, String countryIso2) {
        System.out.println("Calling Legacy Persona API...");
        return 200;
    }
}

// 3. ターゲット契約を実装する具象アダプタ
public class LegacyPersonaAdapter implements KycVerifier {
    private final LegacyPersonaSdk legacySdk;

    public LegacyPersonaAdapter(LegacyPersonaSdk legacySdk) {
        this.legacySdk = legacySdk;
    }

    @Override
    public KycResult verifyUser(UserIdentity identity) {
        // ドメインモデルをレガシー SDK パラメータに変換
        int rawStatusCode = legacySdk.executeCheck(identity.passportNumber(), identity.countryCode());

        // レガシーレスポンスコードをクリーンなドメインレスポンスオブジェクトに変換
        boolean passed = (rawStatusCode == 200);
        return new KycResult(passed, "PERSONA-TX-" + System.currentTimeMillis());
    }
}

// 4. クリーンなビジネスロジック(レガシーベンダーコードから完全に分離)
public class OnboardingService {
    private final KycVerifier kycVerifier;

    // インターフェース経由で注入
    public OnboardingService(KycVerifier kycVerifier) {
        this.kycVerifier = kycVerifier;
    }

    public boolean onboardUser(UserIdentity identity) {
        KycResult result = kycVerifier.verifyUser(identity);
        return result.status();
    }
}

Enter fullscreen mode Exit fullscreen mode

なぜ必要なのか

  • ベンダー非依存: 内部ドメイン契約が外部ベンダーモデルに左右されるのを防ぎます。
  • 変更に対する耐性: ベンダーを切り替えるには新しいアダプタ実装を書くだけで済み、コアビジネスコードは変更されません。

アーキテクチャ上のトレードオフ

  • データマッピングのオーバーヘッド: 内部モデルをベンダー固有のパラメータに変換する際、オブジェクトマッピング中にわずかな CPU とメモリのオーバーヘッドが発生します。
  • 漏洩抽象: ターゲットのレガシーシステムに主要な機能がない場合、アダプタは回避策を講じたり、欠落した機能を隠蔽したりする必要があります。

避けるべき、または却下すべき場合

  • 直接的な 1:1 ミラーインターフェース: シグネチャやフォーマットの変更なしにパラメータを単に渡すだけのアダプタを作成することは、不必要なボイラープレートを追加します。
  • 内部コードの所有権: 呼び出し元と受信者の両方のソースコードを所有および制御している場合、アダプタでラップするのではなく、ターゲット API を直接リファクタリングしてください。

4. Observer パターン

核心概念

Observer パターンは、1 対多の発行購読依存関係を定義します。コアとなる Subject の状態が変化すると、登録された Observer にイベント通知をブロードキャストしますが、Observer の具体的な詳細や実行メカニズムは知りません。

シナリオ: 注文チェックアウトの副作用

eコマースアプリケーションで注文が確定されると、下流のアクションが実行される必要があります: 顧客へのメール通知の送信と倉庫在庫の更新。

❌ 違反アーキテクチャ: ハードコードされた副作用

public class OrderService {
    private final EmailService emailService = new EmailService();
    private final InventoryService inventoryService = new InventoryService();

    public void placeOrder(String orderId, String customerId, double amount) {
        // コアビジネス取引
        System.out.println("Order " + orderId + " saved to DB.");

        // 悪い設計: コアサービスは下流タスクを明示的に知り、同期的に呼び出す
        emailService.sendConfirmationEmail(customerId, orderId);
        inventoryService.deductStock(orderId);
    }
}

Enter fullscreen mode Exit fullscreen mode


✅ リファクタリング後のアーキテクチャ: 分離された発行購読システム

graph TD
    OrderService[OrderService] --> Publisher[OrderPublisher]
    Publisher --> ListenerInterface[OrderEventListener]
    ListenerInterface <|.. EmailListener[EmailNotificationListener]
    ListenerInterface <|.. InventoryListener[InventoryUpdateListener]

Enter fullscreen mode Exit fullscreen mode

コード実装

// 1. 不変のイベントペイロード
public record OrderPlacedEvent(String orderId, String customerId, double amount) {}

// 2. Observer (Subscriber) インターフェース
public interface OrderEventListener {
    void onOrderPlaced(OrderPlacedEvent event);
}

// 3. 具象 Observer(分離された副作用)
public class EmailNotificationListener implements OrderEventListener {
    @Override
    public void onOrderPlaced(OrderPlacedEvent event) {
        System.out.println("Sending email to customer " + event.customerId() + " for order " + event.orderId());
    }
}

public class InventoryUpdateListener implements OrderEventListener {
    @Override
    public void onOrderPlaced(OrderPlacedEvent event) {
        System.out.println("Deducting inventory stock for order " + event.orderId());
    }
}

// 4. Subject / Publisher(購読者を管理し、イベントをブロードキャスト)
public class OrderPublisher {
    private final List<OrderEventListener> listeners = new ArrayList<>();

    public void registerListener(OrderEventListener listener) {
        listeners.add(listener);
    }

    public void publishOrderPlaced(OrderPlacedEvent event) {
        for (OrderEventListener listener : listeners) {
            try {
                listener.onOrderPlaced(event);
            } catch (Exception e) {
                // 他のリスナーが破損しないよう、購読者の失敗を分離する
                System.err.println("Subscriber failed: " + e.getMessage());
            }
        }
    }
}

// 5. クリーンなコアビジネスロジック
public class OrderService {
    private final OrderPublisher publisher;

    public OrderService(OrderPublisher publisher) {
        this.publisher = publisher;
    }

    public void placeOrder(String orderId, String customerId, double amount) {
        // コアビジネスロジック
        System.out.println("Order " + orderId + " saved to DB.");

        // クリーンなイベントブロードキャスト: OrderService は Email や Inventory サービスに直接依存しない
        publisher.publishOrderPlaced(new OrderPlacedEvent(orderId, customerId, amount));
    }
}

Enter fullscreen mode Exit fullscreen mode

なぜ必要なのか

  • コンポーネント間の密結合を排除します。
  • 各リスナーは個別に構築・テストでき、リスナーの失敗は個別に処理できるため、新しいリスナーの追加が既存のフローを破壊しません。
  • 単一責任の原則、開放閉鎖の原則、依存性逆転の原則に従うのに役立ちます。

アーキテクチャ上のトレードオフ

  • 隠された実行チェーンとデバッグの複雑さ: 購読者が動的に登録されるため、ログファイルを通じて制御フローを追跡することが困難になります。状態変化後に何が起こるかをすべて確認するために、IDE で「定義へ移動」することはできなくなります。

避けるべき、または却下すべき場合

  • プロセスに、決して変更されない単一の永続的な副作用がある場合、Observer レジストリを追加することは不必要な間接参照を導入します。

スケールアップ: インメモリ Observer から分散アーキテクチャへ

マイクロサービスアーキテクチャでは、インメモリ Observer は 分散イベント駆動アーキテクチャ (EDA) に進化します。インメモリレジストリを使用する代わりに、サービスは Apache KafkaAWS SNS/SQS、または RabbitMQ などの分散ブローカーにメッセージを発行します。

イベントを失うことなくシステムの一貫性を保つために、本番アーキテクチャではしばしば Transactional Outbox パターン と組み合わせられます:

  1. ビジネスデータとイベントレコードは単一のデータベーストランザクション内で書き込まれます。
  2. 別個のリレープロセスがアウトボックスエントリを読み取り、メッセージブローカーに発行します。
graph LR
    subgraph Microservice Context
        Service[Order Service] --> DB[(Database / Outbox Table)]
    end
    DB --> Relay[Debezium / Outbox Publisher]
    Relay --> Broker[Apache Kafka / RabbitMQ]
    Broker --> Listener1[Notification Service]
    Broker --> Listener2[Inventory Service]

Enter fullscreen mode Exit fullscreen mode


5. Decorator パターン

核心概念

Decorator パターンは、コアコードを変更したり静的なサブクラス爆発を引き起こしたりすることなく、オブジェクトに責任を動的に追加することを可能にします。コアコンポーネントをラップするレイヤーで横断的関心事を追加します。

シナリオ: クレジットカード処理のオプションアドオン

クレジットカード処理サービスは、不正チェックや監査ログなどのオプションの実行時アドオンで拡張できます。

❌ 違反アーキテクチャ: サブクラスの順列カオス

オプションの振る舞いのすべての組み合わせに対してサブクラスを作成すると、クラスの指数関数的な増加を引き起こします。

graph TD
    A[PaymentProcessor Base] --> B[FraudCheckingPaymentProcessor]
    A --> C[AuditLoggingPaymentProcessor]
    A --> D[MetricsPaymentProcessor]
    B --> E[Fraud + Logging PaymentProcessor]
    C --> E
    C --> F[Logging + Metrics PaymentProcessor]
    D --> F
    B --> G[Fraud + Metrics PaymentProcessor]
    D --> G
    E --> H[Fraud + Logging + Metrics PaymentProcessor]
    F --> H
    G --> H

Enter fullscreen mode Exit fullscreen mode

public class BasicPaymentProcessor {
    public void processPayment(double amount) {
        System.out.println("Processing credit card payment of $" + amount);
    }
}

// すべての組み合わせに必要なサブクラスの順列!
public class LoggingPaymentProcessor extends BasicPaymentProcessor { /* ... */ }
public class FraudCheckingPaymentProcessor extends BasicPaymentProcessor { /* ... */ }
public class LoggingAndFraudCheckingPaymentProcessor extends BasicPaymentProcessor { /* ... */ }
// 3 つの機能を追加するとサブクラスが爆発的に増加します!

Enter fullscreen mode Exit fullscreen mode


✅ リファクタリング後のアーキテクチャ: 動的構成チェーン

graph LR
    Client[Client Code] --> FraudDecorator[FraudCheckDecorator]
    FraudDecorator --> AuditDecorator[AuditLoggingDecorator]
    AuditDecorator --> Core[StandardPaymentProcessor]

Enter fullscreen mode Exit fullscreen mode

コード実装

// 1. コンポーネントインターフェース
public interface PaymentProcessor {
    void processPayment(double amount);
}

// 2. 具象ベースコンポーネント(コアドメインロジック)
public class StandardPaymentProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("Executing core payment debit of $" + amount);
    }
}

// 3. 抽象 Decorator(ラップされたコンポーネントへの参照を保持)
public abstract class PaymentProcessorDecorator implements PaymentProcessor {
    protected final PaymentProcessor wrappedProcessor;

    public PaymentProcessorDecorator(PaymentProcessor wrappedProcessor) {
        this.wrappedProcessor = wrappedProcessor;
    }

    @Override
    public void processPayment(double amount) {
        wrappedProcessor.processPayment(amount); // デフォルトの委譲
    }
}

// 4. 具象 Decorator A
public class FraudCheckDecorator extends PaymentProcessorDecorator {
    public FraudCheckDecorator(PaymentProcessor wrappedProcessor) {
        super(wrappedProcessor);
    }

    @Override
    public void processPayment(double amount) {
        System.out.println("[FRAUD CHECK] Validating risk score for amount: $" + amount);
        super.processPayment(amount); // 次のレイヤーに委譲
    }
}

// 具象 Decorator B
public class AuditLoggingDecorator extends PaymentProcessorDecorator {
    public AuditLoggingDecorator(PaymentProcessor wrappedProcessor) {
        super(wrappedProcessor);
    }

    @Override
    public void processPayment(double amount) {
        System.out.println("[AUDIT LOG] Payment initiated at " + System.currentTimeMillis();
        super.processPayment(amount); // 次のレイヤーに委譲
        System.out.println("[AUDIT LOG] Payment execution finished.");
    }
}

// 5. 使用法 / パイプライン構築
public class PaymentApplication {
    public static void main(String[] args) {
        // ベースプロセッサ
        PaymentProcessor processor = new StandardPaymentProcessor();

        // 実行時に処理パイプラインを動的に構成: 不正チェック -> 監査ログ -> コア決済
        PaymentProcessor securePipeline = new FraudCheckDecorator(
            new AuditLoggingDecorator(processor)
        );

        securePipeline.processPayment(250.00);
    }
}

Enter fullscreen mode Exit fullscreen mode

なぜ必要なのか

  • クラス爆発の防止: 静的な継承の組み合わせを動的な構成に置き換えます。
  • 単一責任の原則: 横断的関心事(ロギング、不正チェック、メトリクス)は個別のラッパークラスに分離されます。

アーキテクチャ上のトレードオフ

  • 同一性の危機: デコレートされたインスタンスは、内側のラップされたコンポーネントとオブジェクト同一性を共有しません(decoratedObject != innerObject)。等価チェック(==)やリフレクションベースのロジックが破損する可能性があります。
  • 順序のオーバーヘッド: デコレータが特定の実行順序に依存する場合、ネストされた構成は微妙な実行時バグを引き起こす可能性があります。

避けるべき、または却下すべき場合

  • 厳格な順序依存: Decorator A が常に Decorator B より前に実行されなければならず、順序が乱れるとシステム状態が破壊される場合、ラッパーパイプラインはリスクを追加します。
  • フレームワークインターセプタが利用可能: 現代のエンタープライズフレームワークは、アスペクト指向プログラミング (AOP) やミドルウェア/フィルタパイプライン(例: Spring AOP、Express Middlewares、gRPC Interceptors)を提供しており、手動でオブジェクトラッパーをインスタンス化することなく、横断的関心事をクリーンに処理できます。

💡 完全な 10 ページのシステム設計ガイドを入手する

The Tech Builder Newsletter を購読して、完全な未編集ガイドを無料で即座に入手してください。

購読者は毎週、以下を受け取ります:

  • 🎯 詳細な本番事後分析とシステム設計のトレードオフ分析。
  • 🛠️ 上級 IC、テックリード、アーキテクト向けの実世界のアーキテクチャプレイブック。
  • 🎁 即時ボーナス: 購読すると、完全な 6 ヶ月準備トラッカー&学習スケジュール + 10 ページのシステム設計チートシート を即座に受け取れます。

👉 完全なシステム設計チートシートを入手する