0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-05-17

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

0.実装したい内容

  • 定価がある
  • 顧客によってランクがある
  • 顧客ランクによって値引きされる

1.昔ながらの書き方(条件分岐による値引き)

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

✅ 特徴

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

2.ポリモーフィズム(クラス継承による実装)

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

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

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

✅ 使用例

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

✅ 特徴

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

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

3.デリゲート注入

class 値引き処理
{
    Func<decimal, decimal> 計算ロジック;

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

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

✅ 使用例

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

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

✅ 特徴

  • 実行時にロジックを差し替え可能(柔軟)
  • 匿名関数やラムダで記述できる

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

🧱 主なリスク

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

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

4.比較:それぞれのメリット・デメリット

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

5.使い分けの判断基準

✅ 条件分岐(if)で十分なケース

  • ランクやルールが数パターンしかなく、今後も増える予定がない
  • 小さな関数内で完結し、複雑なロジックが不要

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

✅ クラス継承での実装が適しているケース

  • ルールごとに処理が大きく異なる
  • 各ランクに状態や他のメソッドも持たせたい
  • ルール追加が予想され、保守性を重視したいとき

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

✅ デリゲート注入が有効なケース

  • 処理ロジックを動的に差し替えたい(実行時に変わる)
  • 単一の処理だけ切り替えたい、柔軟性を重視
  • テストや一時的な差し替えに便利(Mockにも使える)

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

6.業務での具体的な適用例

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

7.まとめ:道具は使い分けてこそ

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

X. おまけ(ラムダ式が苦手な方へ:3つの書き方比較)

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

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

✅ 関数を割り当て

int GetLength(string s)
{
    return s.Length;
}
Func<string, int> GetLengthByFunction = GetLength;

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

✅ 匿名デリゲート

Func<string, int> GetLengthByDelegate = delegate(string s) { return s.Length; };

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

✅ ラムダ式

Func<string, int> GetLengthByLambda = s => s.Length;

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?