MENU

Strategyパターンで条件分岐地獄から脱出する

入社2年目の夏、私は絶望的なコードと向き合っていました。

ECサイトの税金計算機能。最初は消費税10%だけのシンプルな仕様だったのに、軽減税率、法人税率、免税事業者対応…と要求が次々に追加されて。

気づいたときには、if文が7層も重なった魔界のようなコードが出来上がっていました。

if (customerType == "一般") {
    if (productType == "食品") {
        if (region == "国内") {
            if (isDiscount) {
                if (discountType == "学生") {
                    // もうここで心が折れる...
                }
            }
        }
    }
}
// この地獄があと100行続く

新しい条件を1つ追加するだけで半日かかる。テストは書けたものじゃない。先輩に「なんだこのコード」と言われる始末。

そんな時、救世主のように現れたのがStrategyパターンでした。

目次

私を救ったシンプルな考え方

Strategyパターンって、要するに「やりたいことは同じ。でもやり方がいくつかある」時に使う設計パターンです。

税金を計算したい ← やりたいこと 一般客・法人・免税事業者で計算方法が違う ← やり方がいくつか

これを別々のクラスに分けて、状況に応じて使い分ける。ただそれだけ。

最初に聞いた時は「は?それだけ?」って思いました。でも実際にやってみたら…

劇的ビフォーアフター

改善前の地獄コード(120行)

  • 条件分岐が7層の入れ子
  • 新機能追加に半日かかる
  • テストが書けない
  • 誰も触りたがらない

改善後の天国コード(35行)

  • 各顧客タイプが独立したクラス
  • 新機能追加が15分で完了
  • 各クラスを個別にテスト可能
  • 後輩も「分かりやすいですね!」

実際のコードはこんな感じになりました。

Step1: 共通インターフェース

// 全ての税率計算戦略が実装するインターフェース
public interface ITaxStrategy
{
    decimal CalculateTaxRate(string productType, string region);
}

「なんだ、メソッド1つだけか」と拍子抜けしました。でもこれが肝なんです。

Step2: 各Strategyクラス

// 一般顧客用
public class StandardCustomerTaxStrategy : ITaxStrategy
{
    public decimal CalculateTaxRate(string productType, string region)
    {
        if (region == "海外") return 0.0m;
        
        return productType switch
        {
            "食品" or "書籍" => 0.08m,
            _ => 0.10m
        };
    }
}

// 法人顧客用(シンプル!)
public class CorporateCustomerTaxStrategy : ITaxStrategy
{
    public decimal CalculateTaxRate(string productType, string region)
    {
        return region == "海外" ? 0.0m : 0.10m;
    }
}

各クラスが独立してるから、一般顧客の計算ロジックをいじっても法人側には影響なし。当たり前のことなのに、これまでのコードではできてなかった。

Step3: Strategyクラスを使う側

public class TaxCalculator
{
    private readonly Dictionary<string, ITaxStrategy> strategies;

    public TaxCalculator()
    {
        strategies = new Dictionary<string, ITaxStrategy>
        {
            ["一般"] = new StandardCustomerTaxStrategy(),
            ["法人"] = new CorporateCustomerTaxStrategy(),
            ["免税事業者"] = new TaxExemptCustomerTaxStrategy()
        };
    }

    public decimal CalculateTax(decimal price, string customerType, string productType, string region)
    {
        var strategy = strategies[customerType];
        return price * strategy.CalculateTaxRate(productType, region);
    }
}

メインの計算クラスがこんなにスッキリ。もう条件分岐の海で溺れる心配はありません。

新機能追加が楽しくなった話

学生割引機能の追加依頼が来たときのことです。

以前なら「うわぁ、またif文追加か…」と憂鬱になっていたところ、今回は15分で完了。

// 新しいStrategyクラスを追加するだけ
public class StudentCustomerTaxStrategy : ITaxStrategy
{
    public decimal CalculateTaxRate(string productType, string region)
    {
        var baseRate = productType switch
        {
            "食品" or "書籍" => 0.08m,
            _ => 0.10m
        };
        return baseRate * 0.8m; // 20%割引
    }
}

// 戦略マップに1行追加
strategies["学生"] = new StudentCustomerTaxStrategy();

既存のコードに一切手を加えることなく新機能完了。

PM「え、もう終わったの?」 私「はい、テストも通ってます」 PM「…天才か?」

いえいえ、Strategyパターンが天才なんです。

実際に使ってみて感じたこと

良かった点

開発効率が10倍になった 新機能追加が半日→15分になったのは大きい。

テストが書きやすい 各戦略クラスを個別にテストできるから、バグの特定が楽。

[Test]
public void 学生_一般商品_20パーセント割引()
{
    var strategy = new StudentCustomerTaxStrategy();
    var rate = strategy.CalculateTaxRate("一般商品", "国内");
    
    // 0.10 * 0.8 = 0.08
    Assert.AreEqual(0.08m, rate);
}

後輩に説明しやすい 「この顧客タイプはこのクラス見て」で済む。

注意点もある

クラスファイルが増える Strategyの数だけファイルが増えるので、小規模なら逆に複雑になるかも。

設計パターンの学習コスト チーム全員が理解していないと、「なんでこんな面倒なことしてるの?」となりがち。

やりすぎ注意 2択の簡単な分岐までStrategyパターンにする必要はない。if文で十分。

いつ使うか、いつ使わないか

私の経験則ですが…

使うべき時

  • 3つ以上の似たような処理パターンがある
  • 将来的に処理パターンが増えそう
  • 各処理が複雑で、個別にテストしたい

使わない方がいい時

  • 処理パターンが2つだけ
  • 各処理が1〜2行で済む
  • チームが設計パターンに慣れていない

私も最初は何でもかんでもパターン適用しようとして、2択の判定までStrategyパターンにして先輩に怒られました。「素直にif文使え」って。

これまでの学習との繋がり

振り返ってみると、これまでのリファクタリング学習が全部繋がってたんです。

  • 変数命名 → 戦略クラス名も分かりやすく
  • 定数化 → 戦略名を定数で管理
  • 処理の共通化 → インターフェースで共通化
  • ファイル分割 → 各戦略を別ファイルに
  • DRY原則 → 重複ロジックを戦略に集約

基礎の積み重ねがあったからこそ、設計パターンも自然に理解できました。

次に何を学ぶか

Strategyパターンをマスターしたら、次はFactoryパターンがおすすめ。

戦略クラスの生成処理をもっとスマートに管理できるようになります。今回作ったTaxCalculatorのコンストラクタ部分がさらに綺麗になりそう。

設定ファイルから動的に戦略を切り替える、なんてこともできるようになります。

最後に

正直、最初は「設計パターンなんて難しそう」と敬遠してました。でも実際に使ってみると、こんなにコードが書きやすくなるとは。

特に新米プログラマーの私にとって、「拡張に強いコード」を書けるようになったのは大きな自信になりました。

もし今、条件分岐の海で溺れている人がいたら、ぜひStrategyパターンを試してみてください。きっと救われます。

私みたいに「天才か?」と言われるかもしれませんよ(実際は天才じゃないですが)。


次回予告 Factoryパターンで、オブジェクト生成をもっとスマートに管理する方法を学びます。Strategyパターンとの組み合わせで、さらに強力な設計が可能に!

参考になったら嬉しいです。一緒に設計パターンマスターを目指しましょう!

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