デリゲート注入ってなに？ - 💰 値引きの実装方法を例にポリモーフィズムとの違い

本記事では「値引き処理」の実装を例に、
昔ながらの条件分岐・ポリモーフィズム・デリゲート注入の違いと使いどころを整理してみました。

## ０．実装したい内容
* 定価がある
* 顧客によってランクがある
* 顧客ランクによって値引きされる

## １．昔ながらの書き方（条件分岐による値引き）

```csharp
decimal 値引き後価格(decimal 定価, string ランク)
{
    switch (ランク)
    {
        case "A":
            return 定価 * 0.60m;
        case "B":
            return 定価 * 0.70m;
        default:
            return 定価;
    }
}
```

✅ 特徴
* シンプルで一目で処理が分かる
* 条件が複雑になると、保守性が下がる
（実務だとここで IF文 が複雑にネストしてたりしますね）


## ２．ポリモーフィズム（クラス継承による実装）

```csharp
abstract class 価格計算
{
    abstract decimal 値引き後価格(decimal 定価);
}

class ランクA購入者 : 価格計算
{
    override decimal 値引き後価格(decimal 定価) => 定価 * 0.60m;
}

class 一般購入者 : 価格計算
{
    override decimal 値引き後価格(decimal 定価) => 定価;
}
```

✅ 使用例
```csharp
var ランクA = new ランクA購入者();
decimal 金額 = ランクA.値引き後価格(定価);
```

✅ 特徴
* 値引きルールが複雑でもクラス単位で明確に分けて実装可能
* 呼び出し側は「値引きという共通の型」だけを意識すればOK
* 新ルール追加も既存コード変更なし（OCPの実現）

:::note
処理の責任が各クラスに分かれていて拡張しやすい
:::

## ３．デリゲート注入
```csharp
class 値引き処理
{
    Func<decimal, decimal> 計算ロジック;

    値引き処理(Func<decimal, decimal> 計算ロジック)
    {
        this.計算ロジック = 計算ロジック;
    }

    decimal 値引き後価格(decimal 定価)
    {
        return 計算ロジック(定価);
    }
}
```

✅ 使用例
```csharp
var ランクA = new 値引き処理(x => x * 0.60m);
var ランクB = new 値引き処理(x => x * 0.70m);
var 一般 = new 値引き処理(x => x);

decimal 金額 = ランクA.値引き後価格(定価);
```

✅ 特徴
* 実行時にロジックを差し替え可能（柔軟）
* 匿名関数やラムダで記述できる

:::note
金額を求める直前で、値引き処理の中身を差し替えられる柔軟性
:::

🧱 主なリスク
* どの処理が実行されたかが、コード上で一見して分かりづらい
* 注入される関数がどこでどう決まるのか把握しづらい
* ラムダや匿名関数は名前がないため、ログに処理名を残せない

:::note alert
柔軟な反面、追跡やデバッグが難しい
:::



## ４．比較：それぞれのメリット・デメリット

| 実装方法     | メリット                 | デメリット                   |
| -------- | -------------------- | ----------------------- |
| 条件分岐（if） | シンプル、動作が見える          | 変更や拡張に弱い、条件が増えると煩雑      |
| クラス継承    | 責務の分離、OCPに沿った拡張がしやすい | クラスが増えすぎると煩雑、継承構造の把握が必要 |
| デリゲート注入  | 柔軟・動的に処理差し替えができる     | どこで何が設定されたか分かりづらい       |

## ５．使い分けの判断基準

### ✅ 条件分岐（if）で十分なケース
* ランクやルールが数パターンしかなく、今後も増える予定がない
* 小さな関数内で完結し、複雑なロジックが不要

> 🚩 例：「社内ツールの簡易バッチ」「試作コード」

### ✅ クラス継承での実装が適しているケース
* ルールごとに処理が大きく異なる
* 各ランクに状態や他のメソッドも持たせたい
* ルール追加が予想され、保守性を重視したいとき

> 🚩 例：「出力帳票形式の切り替え」「会員種別による処理分岐」

### ✅ デリゲート注入が有効なケース
* 処理ロジックを動的に差し替えたい（実行時に変わる）
* 単一の処理だけ切り替えたい、柔軟性を重視
* テストや一時的な差し替えに便利（Mockにも使える）

> 🚩 例：「テスト中だけ特別な値引き」「UIからユーザーがルール選択」

## ６．業務での具体的な適用例

| シーン           | 適用方法    | 理由                     |
| ------------- | ------- | ---------------------- |
| 小規模ツールでの単純な割引 | 条件分岐    | シンプルで実装コストが低い          |
| 社員ランクに応じた手当計算 | クラス継承   | ランクに応じて他の処理も絡む可能性があるため |
| 特売期間中の一時的な割引  | デリゲート注入 | 実行時に設定で切り替えたい          |


## ７．まとめ：道具は使い分けてこそ

「デリゲート注入」も、目的に応じて正しく選べば強力な武器になります。
ただ、個人的には「デリゲート注入」は柔軟過ぎて、多用すると後が怖いです。
大切なのは「変更されるものを外に出す」＝責務の分離と、
将来の変化に耐えられる設計にする事だと思います。


## X. おまけ（ラムダ式が苦手な方へ：3つの書き方比較）

::: note
以下の3種類のコードは全て同じ意味です
:::

* 文字列を受け取り、文字列数を返す

### ✅ 関数を割り当て
```csharp
int GetLength(string s)
{
    return s.Length;
}
Func<string, int> GetLengthByFunction = GetLength;

// 実行例
int len = GetLengthByFunction("ねこ");  // len = 2
```

### ✅ 匿名デリゲート
```csharp
Func<string, int> GetLengthByDelegate = delegate(string s) { return s.Length; };

// 実行例
int len = GetLengthByDelegate("いぬ");  // len = 2
```

### ✅ ラムダ式
```csharp
Func<string, int> GetLengthByLambda = s => s.Length;

// 実行例
int len = GetLengthByLambda("とり");  // len = 2
```