【第2部】 switch文の増殖はバグの温床。ストラテジーパターンで条件分岐を撲滅する

この記事は、保守性の高いコード設計シリーズの一部です。前回の記事【第1部】 ifのネストはもうやめよう。ガード節で可読性を劇的に改善するテクニックでは、ガード節を使ってif文のネストを解消する方法を学びました。

こんにちは。条件分岐には、if文の他にswitch文があります。特定の状態に応じて処理を振り分ける際に便利な構文ですが、このswitch文がシステムの複数箇所で重複し始めると、それは危険信号です。仕様変更の際に修正漏れを引き起こし、追跡困難なバグの温床となります。

今回は、この**「重複するswitch文」の問題を、オブジェクト指向の真髄とも言えるポリモーフィズムストラテジーパターン**を用いて、いかにエレガントに解決するかを解説します。

目次

switch文の増殖が引き起こす悲劇

オンラインストアの配送方法を例に考えてみましょう。「通常配送」「お急ぎ便」「クール便」の3種類があるとします。

【Before】配送方法の種類(ShippingType)に応じて、処理を切り替えるswitch

// 配送料を計算するメソッド
int calculateFee(ShippingType type) {
    switch (type) {
        case STANDARD: return 500;
        case EXPRESS: return 800;
        case REFRIGERATED: return 1200;
    }
    return 0; // default
}

// お届け日数を計算するメソッド
int getEstimatedDays(ShippingType type) {
    switch (type) {
        case STANDARD: return 5;
        case EXPRESS: return 2;
        case REFRIGERATED: return 3;
    }
    return 0; // default
}

ここまでは問題ないように見えます。しかし、ある日**「超お急ぎ便」**という新しい配送方法が追加されたらどうなるでしょう?

開発者は、calculateFeeメソッドにcase SUPER_EXPRESS:を追加しました。しかし、getEstimatedDaysメソッドへの追加をうっかり忘れてしまいました。その結果、「超お急ぎ便」のお届け日数が0日と計算されてしまい、顧客に誤った案内をしてしまうバグが発生します。

このように、同じ条件で分岐するswitch文が複数箇所に分散していると、仕様変更時の修正漏れのリスクが非常に高くなります

解決策:ストラテジーパターンで分岐をなくす

この問題を解決するのがストラテジーパターンです。これは、アルゴリズム(戦略)そのものをオブジェクトとして表現し、それらを交換可能にするデザインパターンです。

  1. 共通のインターフェースを定義する: まず、「配送料の計算」や「お届け日数の計算」といった、配送方法に共通の振る舞いをインターフェースとして定義します。
  2. 具体的な戦略クラスを作成する: 次に、「通常配送」「お急ぎ便」といった各種配送方法を、そのインターフェースを実装した具体的なクラスとして作成します。
  3. 移譲する: 最後に、switch文で処理を切り替える代わりに、対応する戦略クラスのオブジェクトに処理を**移譲(デリゲート)**します。

【After】ストラテジーパターンでswitch文を撲滅したコード

ステップ1: インターフェースの定義

// 配送戦略インターフェース
interface ShippingStrategy {
    int calculateFee();
    int getEstimatedDays();
}

ステップ2: 具体的な戦略クラスの実装

class StandardShipping implements ShippingStrategy {
    public int calculateFee() { return 500; }
    public int getEstimatedDays() { return 5; }
}

class ExpressShipping implements ShippingStrategy {
    public int calculateFee() { return 800; }
    public int getEstimatedDays() { return 2; }
}

class RefrigeratedShipping implements ShippingStrategy {
    public int calculateFee() { return 1200; }
    public int getEstimatedDays() { return 3; }
}

ステップ3: switch文をMapによる移譲に置き換える

class ShippingManager {
    private final Map<ShippingType, ShippingStrategy> strategies;

    ShippingManager() {
        strategies = new HashMap<>();
        strategies.put(ShippingType.STANDARD, new StandardShipping());
        strategies.put(ShippingType.EXPRESS, new ExpressShipping());
        strategies.put(ShippingType.REFRIGERATED, new RefrigeratedShipping());
    }

    // switch文が完全に消え、対応する戦略オブジェクトに処理を任せるだけになった
    int calculateFee(ShippingType type) {
        return strategies.get(type).calculateFee();
    }

    int getEstimatedDays(ShippingType type) {
        return strategies.get(type).getEstimatedDays();
    }
}

この設計では、「通常配送」に関するすべてのロジック(料金、日数など)はStandardShippingクラスに凝集しています。

「超お急ぎ便」を追加したくなった場合、私たちはSuperExpressShippingという新しいクラスを作り、strategiesMapに追加するだけです。既存のコードを修正する必要はなく、calculateFeegetEstimatedDaysの両方を実装し忘れるというミスも起こりません。

まとめ

重複するswitch文は、低凝集と修正漏れのリスクを示す危険なサインです。 ストラテジーパターンを活用し、条件分岐そのものをオブジェクトに置き換えることで、以下のようなメリットが得られます。

  • 高凝集: 関連するロジックが、一つのクラスにまとまる。
  • 拡張性: 新しい条件(戦略)の追加が、新しいクラスの追加だけで済む(オープン・クローズドの原則)。
  • 堅牢性: 仕様変更時の修正漏れのリスクが劇的に減少する。

switch文を書きそうになったら、一度立ち止まって「これはポリモーフィズムで解決できないか?」と考えてみるのが、中級者への第一歩です。

▼次の記事 次回は、さらに複雑なビジネスルールを扱うための「ポリシーパターン」や、メソッドの意図を曖昧にする「フラグ引数」のリファクタリングなど、より高度な条件分岐の改善テクニックを探求します。

【第3部】 複雑な条件分岐を賢く解決。ポリモーフィズムで実現する柔軟なルールエンジン設計

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次