🧩 Strategy パターン
✅ 設計意図
- 責務の中で「振る舞いが分岐する処理(今回なら割引計算)」をStrategy として切り出す
- 外部から割引のルール(戦略)を注入して、柔軟に切り替えられるようにする
✅ 適用理由
- 割引ロジックが今後増える可能性がある
- 割引ルールをクラスとして独立させてテスト・再利用しやすくする
✅ 向いているシーン
- 処理の選択・切り替えが頻繁に発生する
- 条件による処理分岐が肥大化しやすい
✅ コード例
- 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()
✅ 解説
このコードは、Strategy
パターンを用いて、異なる割引戦略を柔軟に適用できる注文処理システムを実装している。
Strategy
パターンは、アルゴリズムや処理をクラスとして分離し、それを動的に切り替えられるようにするデザインパターン。
1. Strategy パターンの概要
- このコードでは、割引計算のロジックを
DiscountStrategy
インターフェースとその実装クラス (NoDiscount
,StudentDiscount
,MemberDiscount
) に分離している。 StrategyOrderProcessor
クラスは、割引戦略を注入(依存性注入)することで、異なる割引ロジックを柔軟に適用できる。
2. 主なクラスとその役割
DiscountStrategy
インターフェース- 割引計算の共通インターフェースを定義
calculate(base: number): number
メソッドを実装する必要がある
NoDiscount
,StudentDiscount
,MemberDiscount
DiscountStrategy
を実装した具体的な割引戦略クラス- それぞれ異なる割引ロジックを提供する
StrategyOrderProcessor
- 注文処理を行うクラス
- 割引戦略 (
DiscountStrategy
) をコンストラクタで受け取り、confirmOrder メソッドで注文を確定する - 注文確定時に以下の処理を行う:
- 基本価格を計算(
productIds.length \* 1000
) - 割引後の合計金額を計算
- 在庫を減らす(
InventoryService.reduce
) - ユーザーにメールを送信(
EmailService.send
) - ログを記録(
OrderLogger.log
)
- 基本価格を計算(
- 利用例
StrategyOrderProcessor
にStudentDiscount
を注入して、学生割引を適用した注文を処理している
3. UML クラス図
4. 更なる改善案
- テスト可能性の向上:
InventoryService
,EmailService
,OrderLogger
をモック可能な依存性として注入することで、ユニットテストが容易になる - エラーハンドリング: 在庫不足やメール送信失敗時のエラーハンドリングを追加すると、より堅牢な設計になる