14  Strategy Pattern and Dependency Injection

The Strategy Pattern inherently uses Dependency Injection (DI) as its mechanism for providing the algorithm to the Context.

14.1 How DI Appears in Strategy Pattern

┌─────────────────────────────────────────────────────────────────────┐
│              DEPENDENCY INJECTION IN STRATEGY PATTERN               │
└─────────────────────────────────────────────────────────────────────┘

  The Context DOES NOT create its own strategy.
  Instead, it RECEIVES the strategy from outside (injected).

                    ┌──────────────────┐
                    │     Client       │
                    └────────┬─────────┘
                             │
                             │ creates & injects
                             ▼
  ┌─────────────────────────────────────────────────────────┐
  │                                                         │
  │   strategy = LossyCompression()  ◄── Client creates     │
  │                     │                                   │
  │                     ▼                                   │
  │   context.set_strategy(strategy) ◄── Client injects     │
  │                                                         │
  └─────────────────────────────────────────────────────────┘
                             │
                             ▼
                    ┌──────────────────┐
                    │     Context      │
                    ├──────────────────┤
                    │ - strategy ────────────▶ Just USES it
                    │                  │       (doesn't create)
                    └──────────────────┘

14.2 Two Common DI Approaches in Strategy

┌─────────────────────────────────────────────────────────────────────┐
│                    1. SETTER INJECTION                              │
│                    (shown in your diagram)                          │
└─────────────────────────────────────────────────────────────────────┘

    class Context:
        def __init__(self):
            self._strategy = None        # No strategy yet
        
        def set_strategy(self, strategy):   # ◄── Inject later
            self._strategy = strategy

    # Usage:
    context = Context()
    context.set_strategy(SomeStrategy())    # Inject via method
    context.set_strategy(OtherStrategy())   # Can swap anytime!

    ✅ Pros: Can change strategy at runtime
    ❌ Cons: Object may be in invalid state (no strategy set)


┌─────────────────────────────────────────────────────────────────────┐
│                    2. CONSTRUCTOR INJECTION                         │
│                    (alternative approach)                           │
└─────────────────────────────────────────────────────────────────────┘

    class Context:
        def __init__(self, strategy: Strategy):  # ◄── Inject at creation
            self._strategy = strategy

    # Usage:
    context = Context(SomeStrategy())    # Must provide strategy upfront

    ✅ Pros: Object always valid, clear dependencies
    ❌ Cons: Need new Context to change strategy (or add setter too)

14.3 Python Example: Both Injection Types

from abc import ABC, abstractmethod


# Strategy Interface
class PaymentStrategy(ABC):
    @abstractmethod
    def pay(self, amount: float) -> None:
        pass


# Concrete Strategies
class CreditCardPayment(PaymentStrategy):
    def pay(self, amount: float) -> None:
        print(f"💳 Paid ฿{amount:,.2f} via Credit Card")


class QRPayment(PaymentStrategy):
    def pay(self, amount: float) -> None:
        print(f"📱 Paid ฿{amount:,.2f} via PromptPay QR")


class CashPayment(PaymentStrategy):
    def pay(self, amount: float) -> None:
        print(f"💵 Paid ฿{amount:,.2f} in Cash")


# ═══════════════════════════════════════════════════════════════
# CONTEXT WITH CONSTRUCTOR INJECTION
# ═══════════════════════════════════════════════════════════════
class PaymentProcessorV1:
    """Strategy injected via constructor."""
    
    def __init__(self, strategy: PaymentStrategy):
        self._strategy = strategy  # Injected at construction
    
    def checkout(self, amount: float) -> None:
        print(f"Processing payment of ฿{amount:,.2f}...")
        self._strategy.pay(amount)


# ═══════════════════════════════════════════════════════════════
# CONTEXT WITH SETTER INJECTION
# ═══════════════════════════════════════════════════════════════
class PaymentProcessorV2:
    """Strategy injected via setter method."""
    
    def __init__(self):
        self._strategy: PaymentStrategy | None = None
    
    def set_strategy(self, strategy: PaymentStrategy) -> None:
        self._strategy = strategy  # Injected via setter
    
    def checkout(self, amount: float) -> None:
        if self._strategy is None:
            raise ValueError("Payment strategy not set!")
        print(f"Processing payment of ฿{amount:,.2f}...")
        self._strategy.pay(amount)


# ═══════════════════════════════════════════════════════════════
# CONTEXT WITH BOTH (Most Flexible)
# ═══════════════════════════════════════════════════════════════
class PaymentProcessorV3:
    """Strategy injected via constructor with optional setter."""
    
    def __init__(self, strategy: PaymentStrategy):
        self._strategy = strategy
    
    def set_strategy(self, strategy: PaymentStrategy) -> None:
        self._strategy = strategy  # Can change later if needed
    
    def checkout(self, amount: float) -> None:
        print(f"Processing payment of ฿{amount:,.2f}...")
        self._strategy.pay(amount)


# ═══════════════════════════════════════════════════════════════
# USAGE EXAMPLES
# ═══════════════════════════════════════════════════════════════
def main():
    print("=" * 50)
    print("V1: Constructor Injection")
    print("=" * 50)
    
    processor1 = PaymentProcessorV1(CreditCardPayment())
    processor1.checkout(1500.00)
    # To change strategy, need new instance:
    processor1_new = PaymentProcessorV1(QRPayment())
    processor1_new.checkout(750.00)
    
    print("\n" + "=" * 50)
    print("V2: Setter Injection")
    print("=" * 50)
    
    processor2 = PaymentProcessorV2()
    processor2.set_strategy(QRPayment())
    processor2.checkout(300.00)
    processor2.set_strategy(CashPayment())  # Swap at runtime
    processor2.checkout(50.00)
    
    print("\n" + "=" * 50)
    print("V3: Both (Constructor + Setter)")
    print("=" * 50)
    
    processor3 = PaymentProcessorV3(CreditCardPayment())
    processor3.checkout(2000.00)
    processor3.set_strategy(QRPayment())  # Can still swap
    processor3.checkout(500.00)


if __name__ == "__main__":
    main()

14.4 Output

==================================================
V1: Constructor Injection
==================================================
Processing payment of ฿1,500.00...
💳 Paid ฿1,500.00 via Credit Card
Processing payment of ฿750.00...
📱 Paid ฿750.00 via PromptPay QR

==================================================
V2: Setter Injection
==================================================
Processing payment of ฿300.00...
📱 Paid ฿300.00 via PromptPay QR
Processing payment of ฿50.00...
💵 Paid ฿50.00 in Cash

==================================================
V3: Both (Constructor + Setter)
==================================================
Processing payment of ฿2,000.00...
💳 Paid ฿2,000.00 via Credit Card
Processing payment of ฿500.00...
📱 Paid ฿500.00 via PromptPay QR

14.5 The Relationship

┌─────────────────────────────────────────────────────────────────────┐
│           STRATEGY PATTERN vs DEPENDENCY INJECTION                  │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────┐      ┌─────────────────────────┐
│   STRATEGY PATTERN      │      │  DEPENDENCY INJECTION   │
├─────────────────────────┤      ├─────────────────────────┤
│                         │      │                         │
│  WHAT: A behavioral     │      │  HOW: A technique for   │
│  pattern for swapping   │      │  providing dependencies │
│  algorithms             │      │  to objects             │
│                         │      │                         │
└───────────┬─────────────┘      └─────────────┬───────────┘
            │                                  │
            │         ┌───────────┐            │
            └────────▶│  OVERLAP  │◀───────────┘
                      ├───────────┤
                      │           │
                      │ Strategy  │
                      │ Pattern   │
                      │ USES DI   │
                      │ to inject │
                      │ algorithms│
                      │           │
                      └───────────┘

  Strategy Pattern = WHAT to inject (algorithm/behavior)
  Dependency Injection = HOW to inject (mechanism)

14.6 Key Insight

┌─────────────────────────────────────────────────────────────────┐
│  Without DI (tight coupling):                                   │
│  ─────────────────────────────                                  │
│                                                                 │
│  class Context:                                                 │
│      def __init__(self):                                        │
│          self._strategy = ConcreteStrategyA()  # ❌ Hardcoded!  │
│                                                                 │
│  • Context controls WHICH strategy                              │
│  • Cannot change without modifying Context                      │
│  • Hard to test                                                 │
│                                                                 │
├─────────────────────────────────────────────────────────────────┤
│  With DI (loose coupling):                                      │
│  ─────────────────────────                                      │
│                                                                 │
│  class Context:                                                 │
│      def __init__(self, strategy: Strategy):                    │
│          self._strategy = strategy  # ✅ Injected from outside  │
│                                                                 │
│  • Client controls WHICH strategy                               │
│  • Context only depends on interface                            │
│  • Easy to test with mock strategies                            │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘