🤮

Strategyパターンの本質は「知らないこと」にある

に公開

はじめに

「Contextは具体的な実装を知らない」

インスタンス化のコードを見ると疑問がなんとなく湧いた。

const strategy = new ConcreteStrategyA();
const context = new Context(strategy);

具体的なクラス名を書いているのだから、知っているのでは?

「知らない」のはContextクラス自身

誰が知らないか、だった

class Context {
  private strategy: Strategy;

  constructor(strategy: Strategy) {
    this.strategy = strategy;
  }

  operation(): void {
    this.strategy.operation();
  }
}

ContextクラスのコードにはConcreteStrategyAやConcreteStrategyBといった具体的なクラス名が一切出てこない。Contextが知っているのは「operation()を持つ何か」だけで、それがZipなのかGzipなのかは知らない。

具体的なクラスを決めるのは使う側(mainやエントリーポイント)の責任になる。

const strategy = condition 
  ? new ConcreteStrategyA() 
  : new ConcreteStrategyB();

const context = new Context(strategy);

「知らない」ことの価値

日常生活で考えてみる。

コンビニでの支払いは、現金でもSuicaでもPayPayでも「支払い完了」という結果は同じ。PayPayの内部仕様など知る必要がない。そしてPayPayの仕様が変わっても、人間は勝手に適応できる。

コードはそうはいかない。書いた通りにしか動かないので

もしContextが具体的な実装を知っている書き方だと、こうなる。

class Context {
  operation(type: string): void {
    if (type === "zip") {
      // Zipの処理
    } else if (type === "gzip") {
      // Gzipの処理
    } else if (type === "bzip2") {
      // Bzip2の処理
    }
  }
}

新しい圧縮方式を追加 → Contextを修正
Gzipの仕様変更 → Contextを修正
Bzip2にバグ → Contextも調査対象

知っていることが増えるほど、変更の影響範囲が広がる。

「知らない」設計の場合

class Lz4 implements Compression {
  compress(data: string): string { ... }
}

const context = new Context(new Lz4());
context.operation();

Contextのコードに触れずに機能追加できる。触らなければバグを入れるリスクもないし、テストのやり直しも最小限で済む。

まとめ

Strategyパターンは「目的は同じで、やり方だけ違う」処理を整理するパターン。

  • 圧縮する → Zip / Gzip / Bzip2
  • 支払う → 現金 / Suica / PayPay
  • 保存する → S3 / GCS / ローカル

やり方が増えても変わっても、Contextには影響させない。変化する部分を隔離しておくことで、既存コードを触らずに拡張できる。

人間なら「知らなくても困らない」のは当たり前だが、コードでは意識的にそういう設計を作る必要がある。

Discussion