
Strategyパターンは「アルゴリズム(処理手順)」を切り替え可能にするデザインパターン。
クラスの中で「条件分岐(if/switch)」を増やさずに、動作を差し替えることができる。
「使う側(コンテキスト)」と「アルゴリズムの実装(ストラテジー)」を分離するのが特徴。
構成
| 定義 |
説明 |
| Strategy(戦略インターフェース) |
アルゴリズム共通のメソッド定義。 |
| ConcreteStrategy(具体的戦略) |
異なるアルゴリズムをそれぞれ実装。 |
| Context(文脈) |
どのStrategyを使うかを外部から設定して利用。 |
サンプルコード
支払い方法を切り替えるパターンのサンプルコードです。
Strategyインターフェース
class PaymentStrategy {
pay(amount) { throw new Error("実装してください"); }
}
Concrete Strategies
class CreditCardPayment extends PaymentStrategy {
pay(amount) { console.log(`💳 クレジットカードで${amount}円を支払いました`); }
}
class PayPalPayment extends PaymentStrategy {
pay(amount) { console.log(`💻 PayPalで${amount}円を支払いました`); }
}
class BankTransferPayment extends PaymentStrategy {
pay(amount) { console.log(`🏦 銀行振込で${amount}円を支払いました`); }
}
Context
class ShoppingCart {
constructor(strategy) {
this.strategy = strategy;
}
checkout(amount) {
this.strategy.pay(amount);
}
}
利用例
const cart1 = new ShoppingCart(new CreditCardPayment());
cart1.checkout(1000);
const cart2 = new ShoppingCart(new PayPalPayment());
cart2.checkout(2000);
メリット
・アルゴリズムを動的に切り替え可能
・if/switchの乱用を防げる
・オープン/クローズド原則(OCP)に従う(新しい戦略を追加しても既存コードに影響しない)
デメリット
・クラス数が増える(戦略ごとに1クラス)
・単純な処理では設計が大げさになりがち
使いどころ
・条件分岐でアルゴリズムを切り替えている部分をポリモーフィズム化したいとき
・「並べ替え方」「圧縮方式」「支払い手段」などを動的に変更したい場合
canvasデモ
Canvas上で「描画ストラテジーを切り替える」Strategyパターンのデモ
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Strategy Pattern Canvas Demo</title>
<style>
body { font-family: sans-serif; text-align: center; margin-top: 20px; }
canvas { border: 1px solid #999; margin-top: 10px; background: #fafafa; }
button { margin: 5px; padding: 8px 16px; border-radius: 8px; cursor: pointer; }
</style>
</head>
<body>
<h2>🎨 Strategyパターン: 描画スタイル切り替え</h2>
<div>
<button id="circleBtn">円を描く</button>
<button id="squareBtn">四角を描く</button>
<button id="triangleBtn">三角を描く</button>
</div>
<canvas id="canvas" width="400" height="300"></canvas>
<script>
// === Strategyインターフェース ===
class DrawStrategy {
draw(ctx) { throw new Error("draw()を実装してください"); }
}
// === Concrete Strategies ===
class CircleStrategy extends DrawStrategy {
draw(ctx) {
ctx.beginPath();
ctx.arc(200, 150, 80, 0, Math.PI * 2);
ctx.fillStyle = "#ff6666";
ctx.fill();
ctx.stroke();
}
}
class SquareStrategy extends DrawStrategy {
draw(ctx) {
ctx.fillStyle = "#66ccff";
ctx.fillRect(120, 80, 160, 160);
ctx.strokeRect(120, 80, 160, 160);
}
}
class TriangleStrategy extends DrawStrategy {
draw(ctx) {
ctx.beginPath();
ctx.moveTo(200, 60);
ctx.lineTo(120, 240);
ctx.lineTo(280, 240);
ctx.closePath();
ctx.fillStyle = "#99cc66";
ctx.fill();
ctx.stroke();
}
}
// === Context ===
class CanvasContext {
constructor(canvas) {
this.ctx = canvas.getContext("2d");
this.strategy = null;
}
setStrategy(strategy) {
this.strategy = strategy;
this.redraw();
}
clear() {
this.ctx.clearRect(0, 0, 400, 300);
}
redraw() {
this.clear();
if (this.strategy) {
this.strategy.draw(this.ctx);
}
}
}
// === 実行部 ===
const canvas = document.getElementById("canvas");
const painter = new CanvasContext(canvas);
document.getElementById("circleBtn").onclick = () => painter.setStrategy(new CircleStrategy());
document.getElementById("squareBtn").onclick = () => painter.setStrategy(new SquareStrategy());
document.getElementById("triangleBtn").onclick = () => painter.setStrategy(new TriangleStrategy());
</script>
</body>
</html>
あとがき
同じメソッド内で、条件分岐が増えると、ネストが深くなる場合があります。
また、条件が複雑なほど、プログラム自体の理解も難しくなります。
クラスに分けることで、役割ごとの分岐を最小限に抑えて、分かりやすいプログラムコードになります。
ストラテジーパターンは、そうしたコードの難読性を和らげる効果を設計することが可能になるパターンですね。
0 件のコメント:
コメントを投稿