🧩 Strategy Pattern
✅ Intent
- Extract behavior that varies within a responsibility (in this case, discount calculation) into a separate
Strategy
- Inject discount rules (strategies) from the outside to allow flexible switching
✅ Motivation
- The discount logic may need to support additional rules in the future
- Encapsulating discount rules as individual classes improves testability and reusability
✅ When to Use
- When logic needs to be frequently selected or switched at runtime
- When conditional branches tend to grow and become hard to manage
✅ Code Example
- TypeScript
- PHP
- Python
// Strategy インターフェース
interface DiscountStrategy {
calculate(base: number): number;
}
class NoDiscount implements DiscountStrategy {
calculate(base: number): number {
return base;
}
}
class StudentDiscount implements DiscountStrategy {
calculate(base: number): number {
return base * 0.8;
}
}
class MemberDiscount implements DiscountStrategy {
calculate(base: number): number {
return base * 0.9;
}
}
// 注文処理クラス(依存を注入)
class StrategyOrderProcessor {
constructor(
private productIds: string[],
private userEmail: string,
private discountStrategy: DiscountStrategy
) {}
confirmOrder() {
const basePrice = this.productIds.length * 1000;
const total = this.discountStrategy.calculate(basePrice);
InventoryService.reduce(this.productIds);
EmailService.send(
this.userEmail,
`ご注文ありがとうございます。合計: ¥${total}`
);
OrderLogger.log("order-" + Math.random().toString(36).substring(2));
}
}
// 利用例
const strategyProcessor = new StrategyOrderProcessor(
["p01", "p02"],
"hiroshi@example.com",
new StudentDiscount()
);
strategyProcessor.confirmOrder();
<?php
interface DiscountStrategy {
public function calculate(float $base): float;
}
class NoDiscount implements DiscountStrategy {
public function calculate(float $base): float {
return $base;
}
}
class StudentDiscount implements DiscountStrategy {
public function calculate(float $base): float {
return $base * 0.8;
}
}
class MemberDiscount implements DiscountStrategy {
public function calculate(float $base): float {
return $base * 0.9;
}
}
// 外部サービスはダミー定義(実際は別クラスとする想定)
class InventoryService {
public static function reduce(array $productIds): void {
echo "在庫を " . count($productIds) . " 件分減らしました\\n";
}
}
class EmailService {
public static function send(string $email, string $message): void {
echo "メールを {$email} に送信: {$message}\\n";
}
}
class OrderLogger {
public static function log(string $orderId): void {
echo "注文ログを記録: {$orderId}\\n";
}
}
// 注文処理クラス
class StrategyOrderProcessor {
public function __construct(
private array $productIds,
private string $userEmail,
private DiscountStrategy $discountStrategy
) {}
public function confirmOrder(): void {
$basePrice = count($this->productIds) * 1000;
$total = $this->discountStrategy->calculate($basePrice);
InventoryService::reduce($this->productIds);
EmailService::send($this->userEmail, "ご注文ありがとうございます。合計: ¥{$total}");
$orderId = "order-" . substr(md5((string)mt_rand()), 0, 8);
OrderLogger::log($orderId);
}
}
// 利用例
$processor = new StrategyOrderProcessor(
["p01", "p02"],
"hiroshi@example.com",
new StudentDiscount()
);
$processor->confirmOrder();
import random
import string
from abc import ABC, abstractmethod
class DiscountStrategy(ABC):
@abstractmethod
def calculate(self, base: float) -> float:
pass
class NoDiscount(DiscountStrategy):
def calculate(self, base: float) -> float:
return base
class StudentDiscount(DiscountStrategy):
def calculate(self, base: float) -> float:
return base * 0.8
class MemberDiscount(DiscountStrategy):
def calculate(self, base: float) -> float:
return base * 0.9
# 外部サービス(ダミー)
class InventoryService:
@staticmethod
def reduce(product_ids: list[str]):
print(f"在庫を {len(product_ids)} 件分減らしました")
class EmailService:
@staticmethod
def send(email: str, message: str):
print(f"メールを {email} に送信: {message}")
class OrderLogger:
@staticmethod
def log(order_id: str):
print(f"注文ログを記録: {order_id}")
# 注文処理クラス
class StrategyOrderProcessor:
def __init__(self, product_ids: list[str], user_email: str, discount_strategy: DiscountStrategy):
self.product_ids = product_ids
self.user_email = user_email
self.discount_strategy = discount_strategy
def confirm_order(self):
base_price = len(self.product_ids) * 1000
total = self.discount_strategy.calculate(base_price)
InventoryService.reduce(self.product_ids)
EmailService.send(self.user_email, f"ご注文ありがとうございます。合計: ¥{total}")
order_id = "order-" + ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
OrderLogger.log(order_id)
# 利用例
processor = StrategyOrderProcessor(
["p01", "p02"],
"hiroshi@example.com",
StudentDiscount()
)
processor.confirm_order()
✅ Explanation
This code implements an order processing system that applies different discount strategies using the Strategy
pattern.
The Strategy
pattern enables algorithms or behaviors to be encapsulated as separate classes and dynamically switched at runtime.
1. Overview of the Strategy Pattern
- The discount logic is decoupled using the
DiscountStrategy
interface and its implementations (NoDiscount
,StudentDiscount
,MemberDiscount
) - The
StrategyOrderProcessor
class accepts a discount strategy through dependency injection, allowing it to flexibly apply different discount rules
2. Key Classes and Their Roles
-
DiscountStrategy
interface- Defines a common interface for discount calculations
- Requires implementation of the
calculate(base: number): number
method
-
NoDiscount
,StudentDiscount
,MemberDiscount
- Concrete strategy classes that implement
DiscountStrategy
- Each provides a different discount logic
- Concrete strategy classes that implement
-
StrategyOrderProcessor
- Handles order processing
- Receives a
DiscountStrategy
instance through its constructor and confirms the order using theconfirmOrder
method - Performs the following steps when confirming an order:
- Calculates the base price (
productIds.length * 1000
) - Calculates the total price after applying the discount
- Reduces inventory (
InventoryService.reduce
) - Sends a confirmation email (
EmailService.send
) - Logs the order (
OrderLogger.log
)
- Calculates the base price (
-
Usage Example
- A
StudentDiscount
instance is injected intoStrategyOrderProcessor
to process an order with a student discount
- A
3. UML Class Diagram
4. Possible Improvements
- Improved Testability: Injecting
InventoryService
,EmailService
, andOrderLogger
as dependencies allows for easier mocking during unit tests - Error Handling: Adding error handling for inventory shortages or email sending failures would make the design more robust