# Strategy Pattern

## Problem

A payment processing system needs to support multiple payment methods: credit card, PayPal, and cryptocurrency. If the checkout logic contains `if/else` branches for each method, adding a new payment type requires modifying the checkout code. The payment algorithms are tangled with order processing logic.

## Solution

Extract each payment algorithm into its own **Strategy** class with a common interface. The checkout (Context) holds a reference to a strategy and delegates payment to it. Strategies can be swapped at runtime without changing the context.

**Key participants:**
- **Strategy** — interface declaring the `pay()` method
- **ConcreteStrategy** — CreditCardPayment, PayPalPayment, CryptoPayment
- **Context** — the PaymentProcessor that uses a strategy

## When to Use

- You have multiple algorithms for the same task and need to switch between them
- You want to isolate algorithm logic from the code that uses it
- A class has many conditional branches selecting behavior — replace with strategies
- You want to swap algorithms at runtime based on user input or configuration

## When to Avoid

- Only one algorithm exists and is unlikely to change
- The algorithms are trivial (one-liners) — strategies add unnecessary indirection
- Clients don't need to know about different algorithms (use Template Method instead)

## Pseudocode

```
INTERFACE PaymentStrategy:
    method pay(amount) -> bool
    method name() -> string

CLASS CreditCardPayment IMPLEMENTS PaymentStrategy:
    card_number
    method pay(amount):
        PRINT "Charging ${amount} to card ending in {last_4}"
        RETURN true

CLASS PaymentProcessor:
    strategy: PaymentStrategy

    method set_strategy(strategy):
        self.strategy = strategy

    method checkout(amount):
        PRINT "Processing ${amount} via {strategy.name}"
        RETURN strategy.pay(amount)
```

## Python

```python
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass


class PaymentStrategy(ABC):
    @abstractmethod
    def pay(self, amount: float) -> bool:
        pass

    @property
    @abstractmethod
    def name(self) -> str:
        pass


@dataclass
class CreditCardPayment(PaymentStrategy):
    card_number: str
    expiry: str

    @property
    def name(self) -> str:
        return "Credit Card"

    def pay(self, amount: float) -> bool:
        last4 = self.card_number[-4:]
        print(f"    Charging ${amount:.2f} to card ending in {last4}")
        print(f"    Card expiry: {self.expiry}")
        print(f"    Payment authorized")
        return True


@dataclass
class PayPalPayment(PaymentStrategy):
    email: str

    @property
    def name(self) -> str:
        return "PayPal"

    def pay(self, amount: float) -> bool:
        print(f"    Redirecting to PayPal for ${amount:.2f}")
        print(f"    PayPal account: {self.email}")
        print(f"    Payment confirmed")
        return True


@dataclass
class CryptoPayment(PaymentStrategy):
    wallet_address: str
    currency: str = "BTC"

    @property
    def name(self) -> str:
        return f"Crypto ({self.currency})"

    def pay(self, amount: float) -> bool:
        short_addr = self.wallet_address[:8] + "..."
        print(f"    Sending ${amount:.2f} in {self.currency} to {short_addr}")
        print(f"    Awaiting blockchain confirmation...")
        print(f"    Transaction confirmed")
        return True


class PaymentProcessor:
    def __init__(self) -> None:
        self._strategy: PaymentStrategy | None = None

    def set_strategy(self, strategy: PaymentStrategy) -> None:
        self._strategy = strategy
        print(f"  Payment method set to: {strategy.name}")

    def checkout(self, amount: float) -> bool:
        if not self._strategy:
            print("  No payment method selected!")
            return False
        print(f"  Processing ${amount:.2f} via {self._strategy.name}...")
        success = self._strategy.pay(amount)
        if success:
            print(f"  Order complete!\n")
        return success


def main() -> None:
    processor = PaymentProcessor()

    print("=== Credit Card Payment ===")
    processor.set_strategy(CreditCardPayment("4111111111111234", "12/26"))
    processor.checkout(99.99)

    print("=== PayPal Payment ===")
    processor.set_strategy(PayPalPayment("user@example.com"))
    processor.checkout(49.50)

    print("=== Crypto Payment ===")
    processor.set_strategy(CryptoPayment("1A2b3C4d5E6f7G8h", "ETH"))
    processor.checkout(150.00)


if __name__ == "__main__":
    main()
```

**Output:**
```
=== Credit Card Payment ===
  Payment method set to: Credit Card
  Processing $99.99 via Credit Card...
    Charging $99.99 to card ending in 1234
    Card expiry: 12/26
    Payment authorized
  Order complete!

=== PayPal Payment ===
  Payment method set to: PayPal
  Processing $49.50 via PayPal...
    Redirecting to PayPal for $49.50
    PayPal account: user@example.com
    Payment confirmed
  Order complete!

=== Crypto Payment ===
  Payment method set to: Crypto (ETH)
  Processing $150.00 via Crypto (ETH)...
    Sending $150.00 in ETH to 1A2b3C4d...
    Awaiting blockchain confirmation...
    Transaction confirmed
  Order complete!
```

## Go

```go
package main

import "fmt"

type PaymentStrategy interface {
	Pay(amount float64) bool
	Name() string
}

type CreditCardPayment struct {
	CardNumber string
	Expiry     string
}

func (c *CreditCardPayment) Name() string { return "Credit Card" }
func (c *CreditCardPayment) Pay(amount float64) bool {
	last4 := c.CardNumber[len(c.CardNumber)-4:]
	fmt.Printf("    Charging $%.2f to card ending in %s\n", amount, last4)
	fmt.Printf("    Card expiry: %s\n", c.Expiry)
	fmt.Println("    Payment authorized")
	return true
}

type PayPalPayment struct {
	Email string
}

func (p *PayPalPayment) Name() string { return "PayPal" }
func (p *PayPalPayment) Pay(amount float64) bool {
	fmt.Printf("    Redirecting to PayPal for $%.2f\n", amount)
	fmt.Printf("    PayPal account: %s\n", p.Email)
	fmt.Println("    Payment confirmed")
	return true
}

type CryptoPayment struct {
	WalletAddress string
	Currency      string
}

func (c *CryptoPayment) Name() string { return fmt.Sprintf("Crypto (%s)", c.Currency) }
func (c *CryptoPayment) Pay(amount float64) bool {
	shortAddr := c.WalletAddress[:8] + "..."
	fmt.Printf("    Sending $%.2f in %s to %s\n", amount, c.Currency, shortAddr)
	fmt.Println("    Awaiting blockchain confirmation...")
	fmt.Println("    Transaction confirmed")
	return true
}

type PaymentProcessor struct {
	strategy PaymentStrategy
}

func (p *PaymentProcessor) SetStrategy(s PaymentStrategy) {
	p.strategy = s
	fmt.Printf("  Payment method set to: %s\n", s.Name())
}

func (p *PaymentProcessor) Checkout(amount float64) bool {
	if p.strategy == nil {
		fmt.Println("  No payment method selected!")
		return false
	}
	fmt.Printf("  Processing $%.2f via %s...\n", amount, p.strategy.Name())
	success := p.strategy.Pay(amount)
	if success {
		fmt.Println("  Order complete!\n")
	}
	return success
}

func main() {
	processor := &PaymentProcessor{}

	fmt.Println("=== Credit Card Payment ===")
	processor.SetStrategy(&CreditCardPayment{"4111111111111234", "12/26"})
	processor.Checkout(99.99)

	fmt.Println("=== PayPal Payment ===")
	processor.SetStrategy(&PayPalPayment{"user@example.com"})
	processor.Checkout(49.50)

	fmt.Println("=== Crypto Payment ===")
	processor.SetStrategy(&CryptoPayment{"1A2b3C4d5E6f7G8h", "ETH"})
	processor.Checkout(150.00)
}
```

**Output:**
```
=== Credit Card Payment ===
  Payment method set to: Credit Card
  Processing $99.99 via Credit Card...
    Charging $99.99 to card ending in 1234
    Card expiry: 12/26
    Payment authorized
  Order complete!

=== PayPal Payment ===
  Payment method set to: PayPal
  Processing $49.50 via PayPal...
    Redirecting to PayPal for $49.50
    PayPal account: user@example.com
    Payment confirmed
  Order complete!

=== Crypto Payment ===
  Payment method set to: Crypto (ETH)
  Processing $150.00 via Crypto (ETH)...
    Sending $150.00 in ETH to 1A2b3C4d...
    Awaiting blockchain confirmation...
    Transaction confirmed
  Order complete!
```

## JavaScript

```javascript
class CreditCardPayment {
  constructor(cardNumber, expiry) {
    this.cardNumber = cardNumber;
    this.expiry = expiry;
  }

  get name() { return "Credit Card"; }

  pay(amount) {
    const last4 = this.cardNumber.slice(-4);
    console.log(`    Charging $${amount.toFixed(2)} to card ending in ${last4}`);
    console.log(`    Card expiry: ${this.expiry}`);
    console.log(`    Payment authorized`);
    return true;
  }
}

class PayPalPayment {
  constructor(email) {
    this.email = email;
  }

  get name() { return "PayPal"; }

  pay(amount) {
    console.log(`    Redirecting to PayPal for $${amount.toFixed(2)}`);
    console.log(`    PayPal account: ${this.email}`);
    console.log(`    Payment confirmed`);
    return true;
  }
}

class CryptoPayment {
  constructor(walletAddress, currency = "BTC") {
    this.walletAddress = walletAddress;
    this.currency = currency;
  }

  get name() { return `Crypto (${this.currency})`; }

  pay(amount) {
    const shortAddr = this.walletAddress.slice(0, 8) + "...";
    console.log(`    Sending $${amount.toFixed(2)} in ${this.currency} to ${shortAddr}`);
    console.log(`    Awaiting blockchain confirmation...`);
    console.log(`    Transaction confirmed`);
    return true;
  }
}

class PaymentProcessor {
  constructor() {
    this.strategy = null;
  }

  setStrategy(strategy) {
    this.strategy = strategy;
    console.log(`  Payment method set to: ${strategy.name}`);
  }

  checkout(amount) {
    if (!this.strategy) {
      console.log("  No payment method selected!");
      return false;
    }
    console.log(`  Processing $${amount.toFixed(2)} via ${this.strategy.name}...`);
    const success = this.strategy.pay(amount);
    if (success) {
      console.log(`  Order complete!\n`);
    }
    return success;
  }
}

// --- Demo ---
const processor = new PaymentProcessor();

console.log("=== Credit Card Payment ===");
processor.setStrategy(new CreditCardPayment("4111111111111234", "12/26"));
processor.checkout(99.99);

console.log("=== PayPal Payment ===");
processor.setStrategy(new PayPalPayment("user@example.com"));
processor.checkout(49.50);

console.log("=== Crypto Payment ===");
processor.setStrategy(new CryptoPayment("1A2b3C4d5E6f7G8h", "ETH"));
processor.checkout(150.00);
```

**Output:**
```
=== Credit Card Payment ===
  Payment method set to: Credit Card
  Processing $99.99 via Credit Card...
    Charging $99.99 to card ending in 1234
    Card expiry: 12/26
    Payment authorized
  Order complete!

=== PayPal Payment ===
  Payment method set to: PayPal
  Processing $49.50 via PayPal...
    Redirecting to PayPal for $49.50
    PayPal account: user@example.com
    Payment confirmed
  Order complete!

=== Crypto Payment ===
  Payment method set to: Crypto (ETH)
  Processing $150.00 via Crypto (ETH)...
    Sending $150.00 in ETH to 1A2b3C4d...
    Awaiting blockchain confirmation...
    Transaction confirmed
  Order complete!
```

## Related Patterns

- **Template Method** — Both define algorithms. Strategy uses composition (inject strategy object); Template Method uses inheritance (override steps in subclass). Prefer Strategy over Template Method.
- **State** — Both use objects that alter behavior. Strategy is stateless and selected by the client; State is stateful and transitions automatically based on internal conditions.
- **Factory Method** — Often used to select and create the right Strategy at runtime.
- **Command** — Commands encapsulate a specific action; Strategies encapsulate an interchangeable algorithm. A Command may use a Strategy internally.
- **Decorator** — Decorator adds behavior to existing objects; Strategy swaps the whole algorithm. Often combined: Decorator wraps the object, Strategy selects algorithm inside.
