🤮
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