ストラテジーパターン

アルゴリズムをオブジェクトとしてカプセル化し、実行時に切り替え可能にするデザインパターン

設計パターンオブジェクト指向

ストラテジーパターンとは

ストラテジーパターンは、アルゴリズムをオブジェクト (関数) としてカプセル化し、実行時に切り替え可能にするデザインパターンである。GoF デザインパターンの振る舞いパターンに分類される。

問題: 条件分岐の肥大化

// ❌ 条件分岐が増え続ける
function calculatePrice(type: string, base: number): number {
  if (type === 'regular') return base;
  if (type === 'premium') return base * 0.9;
  if (type === 'vip') return base * 0.8;
  if (type === 'employee') return base * 0.5;
  // 新しい割引タイプが追加されるたびに分岐が増える
  throw new Error(`Unknown type: ${type}`);
}

ストラテジーで解決

type PricingStrategy = (base: number) => number;

const strategies: Record<string, PricingStrategy> = {
  regular: (base) => base,
  premium: (base) => base * 0.9,
  vip: (base) => base * 0.8,
  employee: (base) => base * 0.5,
};

function calculatePrice(type: string, base: number): number {
  const strategy = strategies[type];
  if (!strategy) throw new Error(`Unknown type: ${type}`);
  return strategy(base);
}
// 新しい戦略の追加 = オブジェクトにプロパティを追加するだけ

実用例: ソート戦略

type SortStrategy<T> = (a: T, b: T) => number;

const byName: SortStrategy<User> = (a, b) => a.name.localeCompare(b.name);
const byAge: SortStrategy<User> = (a, b) => a.age - b.age;
const byCreatedAt: SortStrategy<User> = (a, b) => b.createdAt - a.createdAt;

// 実行時に切り替え
users.sort(byName);
users.sort(byAge);

実用例: 通知戦略

type NotifyStrategy = (message: string) => Promise<void>;

const emailNotify: NotifyStrategy = async (msg) => { await ses.send(msg); };
const slackNotify: NotifyStrategy = async (msg) => { await slack.post(msg); };
const snsNotify: NotifyStrategy = async (msg) => { await sns.publish(msg); };

// 環境変数で戦略を切り替え
const notify = strategies[process.env.NOTIFY_TYPE ?? 'email'];
await notify('Order completed');

ストラテジー vs if-else

観点 if-else ストラテジー
拡張性 分岐を追加 オブジェクトにプロパティを追加
テスト 全分岐をテスト 各戦略を個別にテスト
開放閉鎖原則 違反 (既存コードを変更) 準拠 (新しい戦略を追加)

さらに掘り下げるなら関連書籍が参考になる。

関連用語