第10章 Strategy:ふるまいを部品化して差し替え🎭🔁
※2026/1/15時点だと、.NET 10(LTS)は 2025/11/11 リリースで、最新パッチは 10.0.2(2026/1/13)です。(Microsoft) また C# 14 は 2025/11 にリリース済みです。(Microsoft Learn)(C# 14 の新機能一覧も Microsoft Learn にあります)(Microsoft Learn) C# 14 をVSで使うなら、だいたい VS 2022 17.13 以降が目安です。(Microsoft Learn)
0. この章のゴール🎯✨
この章が終わると、こんなことができるようになります😊
- 「巨大な if/switch 地獄😵💫」を、差し替え可能な部品🧩に分解できる
- 新しいルール(割引・手数料・通知など)が増えても、既存コードを壊しにくい🛡️形にできる
- 「継承で増やす」じゃなくて、合成(Composition)で増やす感覚がつく🎉
1. Strategyって何?ひとことで言うと🧠🎭

**「やり方(アルゴリズム)を部品にして、入れ替えできるようにする」**パターンだよ〜🔁✨
- ✅ “やり方”を インターフェースで表す
- ✅ “使う側”は インターフェースだけ知ってればOK
- ✅ “どのやり方を使うか”は 外から差し替え(合成!)
2. まずは「つらいコード」を体験😱(Before)
題材:割引ルール(例:会員・クーポン・まとめ買い…)🛒💸 こういうの、最初はこう書きがち👇
public enum DiscountType
{
None,
Member,
Coupon,
Bulk
}
public sealed class CheckoutService
{
public decimal CalcTotal(decimal subtotal, DiscountType type, string? couponCode, int itemCount)
{
decimal discount = 0m;
if (type == DiscountType.Member)
{
discount = subtotal * 0.05m;
}
else if (type == DiscountType.Coupon)
{
discount = couponCode == "WELCOME10" ? 10m : 0m;
}
else if (type == DiscountType.Bulk)
{
discount = itemCount >= 10 ? subtotal * 0.08m : 0m;
}
return Math.Max(0m, subtotal - discount);
}
}
これ、何がつらいの?🥲
- ルール追加のたびに CheckoutService を編集することになる✍️💥
- if/switch が増えるほど、テストも修正も怖い😨
- 「割引の種類」が増えると、影響範囲が爆発💣
ここで Strategy の出番!🎭✨
3. Strategy化の基本形🧩🔌(構造をイメージ)
登場人物はこれだけ👇
- Strategy(戦略):やり方の部品(例:割引計算のやり方)
- Context(利用側):部品を使う側(例:精算サービス)
- 差し替え(合成):Context は Strategy を “持つ”(has-a)
4. ハンズオン🧪:割引を Strategy 化しよう!🛒✨
4-1. まず「契約」を作る📜(インターフェース)
ポイントは “やり方”を表すメソッドだけを置くこと🙂
public interface IDiscountStrategy
{
decimal CalcDiscount(decimal subtotal, DiscountContext context);
}
public sealed record DiscountContext(
bool IsMember,
string? CouponCode,
int ItemCount
);
DiscountContextは「割引判断に必要な情報のまとめ」📦✨- record が苦手なら class でもOKだよ🙂(ここは好み!)
4-2. “やり方”を部品として実装する🧩🎭
① 割引なし😊
public sealed class NoDiscountStrategy : IDiscountStrategy
{
public decimal CalcDiscount(decimal subtotal, DiscountContext context) => 0m;
}
② 会員割引👑
public sealed class MemberDiscountStrategy : IDiscountStrategy
{
public decimal CalcDiscount(decimal subtotal, DiscountContext context)
=> context.IsMember ? subtotal * 0.05m : 0m;
}
③ クーポン割引🎫
public sealed class CouponDiscountStrategy : IDiscountStrategy
{
public decimal CalcDiscount(decimal subtotal, DiscountContext context)
=> context.CouponCode == "WELCOME10" ? 10m : 0m;
}
④ まとめ買い割引📦📦📦
public sealed class BulkDiscountStrategy : IDiscountStrategy
{
public decimal CalcDiscount(decimal subtotal, DiscountContext context)
=> context.ItemCount >= 10 ? subtotal * 0.08m : 0m;
}
4-3. 使う側(Context)を “差し替え可能” にする🔁✨
ここが Strategy の気持ちいいところ😍 CheckoutService は 割引の中身を知らない!
public sealed class CheckoutService
{
private readonly IDiscountStrategy _discountStrategy;
public CheckoutService(IDiscountStrategy discountStrategy)
{
_discountStrategy = discountStrategy;
}
public decimal CalcTotal(decimal subtotal, DiscountContext context)
{
var discount = _discountStrategy.CalcDiscount(subtotal, context);
return Math.Max(0m, subtotal - discount);
}
}
4-4. 組み立て(合成)して動かす🧩🚀(超シンプル版)
var subtotal = 120m;
var context = new DiscountContext(
IsMember: true,
CouponCode: null,
ItemCount: 3
);
// 差し替えポイント🎭
IDiscountStrategy strategy = new MemberDiscountStrategy();
var checkout = new CheckoutService(strategy);
var total = checkout.CalcTotal(subtotal, context);
Console.WriteLine($"Total = {total}");
✅ “差し替え”ってこういうこと!
strategy を変えるだけで挙動が変わるよ〜🔁✨
5. 「でも実際は、状況で自動選択したい😅」→ Resolver を足そう🔎🧠

現場あるある: 「会員なら会員割引、クーポンがあればクーポン割引…」みたいに、状況で戦略を選びたいよね🙂
そこで Strategy を選ぶ専用クラスを作るよ!✨ (CheckoutService を if 地獄に戻さないための工夫🛡️)
5-1. “選ぶ係”の契約📜
public interface IDiscountStrategyResolver
{
IDiscountStrategy Resolve(DiscountContext context);
}
5-2. まずは素朴に if で選ぶ(※ここに隔離する!)🧺
public sealed class DiscountStrategyResolver : IDiscountStrategyResolver
{
private readonly IDiscountStrategy _no;
private readonly IDiscountStrategy _member;
private readonly IDiscountStrategy _coupon;
private readonly IDiscountStrategy _bulk;
public DiscountStrategyResolver(
NoDiscountStrategy no,
MemberDiscountStrategy member,
CouponDiscountStrategy coupon,
BulkDiscountStrategy bulk)
{
_no = no;
_member = member;
_coupon = coupon;
_bulk = bulk;
}
public IDiscountStrategy Resolve(DiscountContext context)
{
if (context.CouponCode is not null) return _coupon;
if (context.ItemCount >= 10) return _bulk;
if (context.IsMember) return _member;
return _no;
}
}
- if は ゼロにしなくてOK🙆♀️
- 大事なのは、**増殖しそうなifを “1か所に閉じ込める”**こと🧺✨
5-3. CheckoutService はスッキリのまま😍
public sealed class CheckoutService
{
private readonly IDiscountStrategyResolver _resolver;
public CheckoutService(IDiscountStrategyResolver resolver)
{
_resolver = resolver;
}
public decimal CalcTotal(decimal subtotal, DiscountContext context)
{
var strategy = _resolver.Resolve(context);
var discount = strategy.CalcDiscount(subtotal, context);
return Math.Max(0m, subtotal - discount);
}
}
6. DIコンテナと相性よすぎ問題🤝🤖(最小だけ)
「部品が増えてきたら、組み立てを自動化したい」ってなるよね🙂 ここでは 最小だけやるね(深入りしない!)🧰✨
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
// Strategy 登録🎭
services.AddSingleton<NoDiscountStrategy>();
services.AddSingleton<MemberDiscountStrategy>();
services.AddSingleton<CouponDiscountStrategy>();
services.AddSingleton<BulkDiscountStrategy>();
// Resolver / Service 登録🧩
services.AddSingleton<IDiscountStrategyResolver, DiscountStrategyResolver>();
services.AddSingleton<CheckoutService>();
var sp = services.BuildServiceProvider();
var checkout = sp.GetRequiredService<CheckoutService>();
var total = checkout.CalcTotal(
subtotal: 120m,
context: new DiscountContext(IsMember: true, CouponCode: null, ItemCount: 3)
);
Console.WriteLine(total);
7. よくある失敗あるある⚠️😵💫(先に潰す!)
失敗①:Strategy を「何でも屋」にする🧙♂️💥
CalcDiscount()の中でDBアクセスしたり、ログ出したり、通知したり… → それ、責務モリモリで後で泣く😭
✅ コツ:Strategy は **“やり方(計算/判断)だけ”**に寄せる🧮✨ 外部I/Oは別部品に逃がす(第15章でさらに嬉しくなるやつ!)✅
失敗②:Context がデカすぎる📦📦📦
- なんでも
DiscountContextに入れちゃうと、設計がぼやける😶🌫️
✅ コツ:**「この戦略を選ぶのに必要な情報だけ」**に絞る✂️✨
失敗③:Resolver の if が増えてまた地獄😱
- ルールが100個になったら…Resolverが肥大化💣
✅ コツ:増えてきたら次の段階へ(例)
- 優先順位テーブル化📋
- “適用できるか” を Strategy 側に持たせる(
CanApply()) Dictionaryでキー検索(O(1))📌
(このへんは第16章の「安全移行」と相性よいよ🙂)
8. AI活用コーナー🤖🫶(写経じゃなく“相棒”にする)
使いどころ①:Strategy候補の洗い出し🔍
例プロンプト💬
- 「割引仕様がこれ。Strategy に分ける候補を列挙して。責務も一言で」
- 「将来追加されそうなルールを3つ予想して、それに強い設計にしたい」
使いどころ②:命名相談📝✨
- 「
IDiscountStrategyとDiscountContextの命名もっと良くして」 - 「このメソッド名、女子大生が読んでも分かるように変えて🥹」
使いどころ③:レビュー🕵️♀️✅
- 「CheckoutService が Strategy に依存できてる?変更に弱い点は?」
- 「責務が混ざってるクラスがあれば指摘して」
⚠️ 注意:AIの出力は“提案”なので、最終判断は自分でね🙂✨
9. ミニ課題(宿題でもOK)📮✨
課題A:新しい割引を1個追加🎁
例:
- 「誕生日割引🎂」
- 「初回購入割引🌱」
- 「曜日割引📅」
✅ 条件:CheckoutService は修正しない(できたら勝ち!🏆)
課題B:優先順位を変える🔁
「クーポンがあっても、まとめ買いの方を優先したい」みたいな変更を、 Resolver の1か所だけで対応できるようにしてみてね🙂
10. まとめ🌈🎉(今日覚えたこと)
- Strategy は “やり方”を部品化して差し替えるパターン🎭🔁
- 巨大 if/switch を 部品(クラス)に逃がすと、追加に強くなる🛡️
- “選ぶ処理”が必要なら、Resolver に 隔離して合成を守る🧺✨
- DI と相性よくて、部品が増えても運用しやすい🤝🤖
次の章(State)に行くと、「状態による if 地獄🚦😵💫」も同じノリで救えるようになるよ〜!✨