
Template Method(テンプレートメソッド)は、「処理の流れ(テンプレート)」を親クラスに定義し、その中の一部の処理だけをサブクラスに任せるパターンです。
「手順の型は同じだけど、細部だけ変えたい」場合に最適なパターンですね。
料理の手順に例えるなら、
共通(材料を準備 → 加熱 → 盛り付け)の順に処理をする際、
料理の種類(カレー・ラーメンなど) によって中身は違うというような場合に。
共通の流れはテンプレート化して、差分だけを各サブクラスに任せるようにする。
構成
Abstract Class(抽象クラス)
・一連の処理の流れ(テンプレートメソッド)を定義。
・一部のメソッドを抽象化して、サブクラスが実装。
Concrete Class(具象クラス)
・抽象メソッドを具体的に実装。
サンプルコード1
料理テンプレート
抽象クラス
class CookingTemplate {
cook() {
this.prepare();
this.heat();
this.serve();
}
prepare() { throw new Error("prepare()を実装してください"); }
heat() { throw new Error("heat()を実装してください"); }
serve() { console.log("料理をお皿に盛り付けました!"); }
}
具象クラス1
class CurryCooking extends CookingTemplate {
prepare() { console.log("カレーの材料を切る"); }
heat() { console.log("カレーを煮込む"); }
}
具象クラス2
class RamenCooking extends CookingTemplate {
prepare() { console.log("ラーメンの具材を準備"); }
heat() { console.log("麺を茹でてスープを温める"); }
}
実行
const curry = new CurryCooking();
curry.cook();
// => カレーの材料を切る
// => カレーを煮込む
// => 料理をお皿に盛り付けました!
const ramen = new RamenCooking();
ramen.cook();
// => ラーメンの具材を準備
// => 麺を茹でてスープを温める
// => 料理をお皿に盛り付けました!
サンプルコード2
Canvasでテンプレートメソッドを可視化するデモ
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Template Method 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>🧩 Template Methodパターン: 描画テンプレート</h2>
<div>
<button id="circleBtn">円を描く</button>
<button id="squareBtn">四角を描く</button>
<button id="triangleBtn">三角を描く</button>
</div>
<canvas id="canvas" width="400" height="300"></canvas>
<script>
// === 抽象クラス(テンプレート) ===
class ShapeTemplate {
constructor(ctx) { this.ctx = ctx; }
// Template Method:共通の処理の流れ
draw() {
this.clear();
this.drawShape();
this.drawLabel();
}
clear() {
this.ctx.clearRect(0, 0, 400, 300);
}
// サブクラスに任せる抽象メソッド
drawShape() {
throw new Error("drawShape()を実装してください");
}
drawLabel() {
this.ctx.font = "18px sans-serif";
this.ctx.fillStyle = "#333";
this.ctx.textAlign = "center";
this.ctx.fillText(this.constructor.name.replace("Shape", ""), 200, 270);
}
}
// === 具象クラス1 ===
class CircleShape extends ShapeTemplate {
drawShape() {
const ctx = this.ctx;
ctx.beginPath();
ctx.arc(200, 140, 60, 0, Math.PI * 2);
ctx.fillStyle = "#ff6666";
ctx.fill();
ctx.stroke();
}
}
// === 具象クラス2 ===
class SquareShape extends ShapeTemplate {
drawShape() {
const ctx = this.ctx;
ctx.fillStyle = "#66ccff";
ctx.fillRect(130, 80, 140, 140);
ctx.strokeRect(130, 80, 140, 140);
}
}
// === 具象クラス3 ===
class TriangleShape extends ShapeTemplate {
drawShape() {
const ctx = this.ctx;
ctx.beginPath();
ctx.moveTo(200, 60);
ctx.lineTo(120, 220);
ctx.lineTo(280, 220);
ctx.closePath();
ctx.fillStyle = "#99cc66";
ctx.fill();
ctx.stroke();
}
}
// === 実行環境 ===
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
document.getElementById("circleBtn").onclick = () => new CircleShape(ctx).draw();
document.getElementById("squareBtn").onclick = () => new SquareShape(ctx).draw();
document.getElementById("triangleBtn").onclick = () => new TriangleShape(ctx).draw();
</script>
</body>
</html>
ポイント解説
Template Method:
draw() が「共通の手順」を定義(clear → drawShape → drawLabel)
差分部分:
drawShape() を各サブクラスが独自に実装。
結果:
どの図形でも同じ流れで描かれるが、
形状(中身)だけが変化する。
メリット
・共通処理の流れを1か所で管理できる。
・処理の変更や追加が容易(サブクラス単位で拡張できる)。
・コードの重複が減る。
デメリット
・継承ベースなので、柔軟な差し替え(動的切り替え) が難しい。
・サブクラスが増えると管理が煩雑になる。
よく使う場面
一連の流れが固定で、部分的に差を出したい処理。
・ファイル入出力(共通:open → read/write → close)
・HTMLレンダリング(共通:header → body → footer)
・テスト実行(共通:setup → run → teardown)
あとがき
類似のclass処理で、構成をルール化して、同じメソッドを用意した状態で、それを手順通りに処理するのがテンプレートパターンですね。
手順の構成をしっかりと設計できていないと、エラーにつながりますが、システムの管理は便利になることが分かります。
でも、手順が変わると、全ての手順メソッドを同率に更新する必要があるので、更新管理はもしかしたら一手間かかるかもしれません。
0 件のコメント:
コメントを投稿