コマンド、責任の連鎖、およびテンプレートメソッド
I 行動パターン オブジェクト間の通信と責任を管理します。 指示 リクエストをオブジェクトとしてカプセル化します (元に戻す/やり直し)。 責任の連鎖 ハンドラー (ミドルウェア) のチェーンに沿ってリクエストを渡します。 テンプレートメソッド 定義する カスタマイズ可能なステップを備えたアルゴリズムのスケルトン。
🎯 何を学ぶか
- コマンド パターン: アクションをオブジェクトとしてカプセル化する
- コマンドで元に戻す/やり直しを実装する
- 責任の連鎖: ハンドラーの連鎖
- テンプレートメソッド: フックポイントのあるアルゴリズム
コマンドパターン
Il コマンドパターン リクエストをオブジェクトとしてカプセル化することで、次のことが可能になります。 オペレーション、キューリクエスト、およびサポートオペレーションを使用してメソッドをパラメータ化します。 元に戻すことができます (元に戻す/やり直し)。
// Interfaccia Command
interface Command {{ '{' }}
execute(): void;
undo(): void;
{{ '}' }}
// Receiver: oggetto che esegue l'azione reale
class Light {{ '{' }}
private isOn: boolean = false;
turnOn(): void {{ '{' }}
this.isOn = true;
console.log("💡 Luce accesa");
{{ '}' }}
turnOff(): void {{ '{' }}
this.isOn = false;
console.log("🌑 Luce spenta");
{{ '}' }}
getState(): boolean {{ '{' }}
return this.isOn;
{{ '}' }}
{{ '}' }}
// Concrete Commands
class TurnOnCommand implements Command {{ '{' }}
constructor(private light: Light) {{ '{' }}{{ '}' }}
execute(): void {{ '{' }}
this.light.turnOn();
{{ '}' }}
undo(): void {{ '{' }}
this.light.turnOff();
{{ '}' }}
{{ '}' }}
class TurnOffCommand implements Command {{ '{' }}
constructor(private light: Light) {{ '{' }}{{ '}' }}
execute(): void {{ '{' }}
this.light.turnOff();
{{ '}' }}
undo(): void {{ '{' }}
this.light.turnOn();
{{ '}' }}
{{ '}' }}
// Invoker: esegue i comandi
class RemoteControl {{ '{' }}
private history: Command[] = [];
executeCommand(command: Command): void {{ '{' }}
command.execute();
this.history.push(command);
{{ '}' }}
undo(): void {{ '{' }}
const command = this.history.pop();
if (command) {{ '{' }}
command.undo();
{{ '}' }}
{{ '}' }}
{{ '}' }}
// Utilizzo
const light = new Light();
const remote = new RemoteControl();
remote.executeCommand(new TurnOnCommand(light)); // "💡 Luce accesa"
remote.executeCommand(new TurnOffCommand(light)); // "🌑 Luce spenta"
remote.undo(); // "💡 Luce accesa" (annulla spegnimento)
完全な元に戻す/やり直しを伴うコマンド
複数の元に戻す/やり直しを行う高度な実装:
class TextEditor {{ '{' }}
private content: string = "";
getContent(): string {{ '{' }}
return this.content;
{{ '}' }}
setContent(content: string): void {{ '{' }}
this.content = content;
{{ '}' }}
append(text: string): void {{ '{' }}
this.content += text;
{{ '}' }}
delete(length: number): void {{ '{' }}
this.content = this.content.slice(0, -length);
{{ '}' }}
{{ '}' }}
class AppendCommand implements Command {{ '{' }}
constructor(
private editor: TextEditor,
private text: string
) {{ '{' }}{{ '}' }}
execute(): void {{ '{' }}
this.editor.append(this.text);
{{ '}' }}
undo(): void {{ '{' }}
this.editor.delete(this.text.length);
{{ '}' }}
{{ '}' }}
class DeleteCommand implements Command {{ '{' }}
private deletedText: string = "";
constructor(
private editor: TextEditor,
private length: number
) {{ '{' }}{{ '}' }}
execute(): void {{ '{' }}
const content = this.editor.getContent();
this.deletedText = content.slice(-this.length);
this.editor.delete(this.length);
{{ '}' }}
undo(): void {{ '{' }}
this.editor.append(this.deletedText);
{{ '}' }}
{{ '}' }}
class CommandManager {{ '{' }}
private history: Command[] = [];
private redoStack: Command[] = [];
execute(command: Command): void {{ '{' }}
command.execute();
this.history.push(command);
this.redoStack = []; // Pulisci redo stack su nuova azione
{{ '}' }}
undo(): void {{ '{' }}
const command = this.history.pop();
if (command) {{ '{' }}
command.undo();
this.redoStack.push(command);
{{ '}' }}
{{ '}' }}
redo(): void {{ '{' }}
const command = this.redoStack.pop();
if (command) {{ '{' }}
command.execute();
this.history.push(command);
{{ '}' }}
{{ '}' }}
{{ '}' }}
// Utilizzo
const editor = new TextEditor();
const manager = new CommandManager();
manager.execute(new AppendCommand(editor, "Hello "));
manager.execute(new AppendCommand(editor, "World"));
console.log(editor.getContent()); // "Hello World"
manager.undo();
console.log(editor.getContent()); // "Hello "
manager.redo();
console.log(editor.getContent()); // "Hello World"
責任連鎖パターン
Il 責任の連鎖 複数のオブジェクトがリクエストを処理できるようにする 送信者は誰がそれを処理するか知りません。リクエストはチェーンを介して渡され、 誰かがそれを管理します。
// Handler astratto
abstract class Handler {{ '{' }}
protected next: Handler | null = null;
setNext(handler: Handler): Handler {{ '{' }}
this.next = handler;
return handler; // Fluent interface
{{ '}' }}
handle(request: string): void {{ '{' }}
if (this.canHandle(request)) {{ '{' }}
this.process(request);
{{ '}' }} else if (this.next) {{ '{' }}
this.next.handle(request);
{{ '}' }} else {{ '{' }}
console.log(`❌ Nessun handler per: ${{ '{' }}request{{ '}' }}`);
{{ '}' }}
{{ '}' }}
protected abstract canHandle(request: string): boolean;
protected abstract process(request: string): void;
{{ '}' }}
// Concrete Handlers
class SupportLevel1 extends Handler {{ '{' }}
protected canHandle(request: string): boolean {{ '{' }}
return request.includes("password");
{{ '}' }}
protected process(request: string): void {{ '{' }}
console.log("👤 Level 1: Reset password inviato");
{{ '}' }}
{{ '}' }}
class SupportLevel2 extends Handler {{ '{' }}
protected canHandle(request: string): boolean {{ '{' }}
return request.includes("bug");
{{ '}' }}
protected process(request: string): void {{ '{' }}
console.log("🔧 Level 2: Investigazione bug iniziata");
{{ '}' }}
{{ '}' }}
class SupportLevel3 extends Handler {{ '{' }}
protected canHandle(request: string): boolean {{ '{' }}
return request.includes("crash");
{{ '}' }}
protected process(request: string): void {{ '{' }}
console.log("🚨 Level 3: Crash critico escalato a engineering");
{{ '}' }}
{{ '}' }}
// Setup della catena
const level1 = new SupportLevel1();
const level2 = new SupportLevel2();
const level3 = new SupportLevel3();
level1.setNext(level2).setNext(level3);
// Utilizzo
level1.handle("Ho dimenticato la password"); // "👤 Level 1: Reset password inviato"
level1.handle("C'è un bug nel form"); // "🔧 Level 2: Investigazione bug iniziata"
level1.handle("L'app crasha"); // "🚨 Level 3: Crash critico escalato a engineering"
level1.handle("Domanda generica"); // "❌ Nessun handler per: Domanda generica"
責任の連鎖: HTTP ミドルウェア
実践例: HTTP リクエストを処理するミドルウェア パイプライン:
interface Request {{ '{' }}
url: string;
method: string;
headers: Record<string, string>;
body?: any;
{{ '}' }}
type NextFunction = () => void;
abstract class Middleware {{ '{' }}
protected next: Middleware | null = null;
setNext(middleware: Middleware): Middleware {{ '{' }}
this.next = middleware;
return middleware;
{{ '}' }}
handle(req: Request, next: NextFunction): void {{ '{' }}
this.process(req, () => {{ '{' }}
if (this.next) {{ '{' }}
this.next.handle(req, next);
{{ '}' }} else {{ '{' }}
next();
{{ '}' }}
{{ '}' }});
{{ '}' }}
protected abstract process(req: Request, next: NextFunction): void;
{{ '}' }}
class AuthMiddleware extends Middleware {{ '{' }}
protected process(req: Request, next: NextFunction): void {{ '{' }}
console.log("🔐 Checking authentication...");
if (req.headers['authorization']) {{ '{' }}
console.log("✅ Authenticated");
next();
{{ '}' }} else {{ '{' }}
console.log("❌ Unauthorized - blocking request");
{{ '}' }}
{{ '}' }}
{{ '}' }}
class LoggingMiddleware extends Middleware {{ '{' }}
protected process(req: Request, next: NextFunction): void {{ '{' }}
console.log(`📝 ${{ '{' }}req.method{{ '}' }} ${{ '{' }}req.url{{ '}' }}`);
next();
{{ '}' }}
{{ '}' }}
class ValidationMiddleware extends Middleware {{ '{' }}
protected process(req: Request, next: NextFunction): void {{ '{' }}
console.log("✔️ Validating request...");
if (req.method === 'POST' && !req.body) {{ '{' }}
console.log("❌ Validation failed: missing body");
{{ '}' }} else {{ '{' }}
console.log("✅ Validation passed");
next();
{{ '}' }}
{{ '}' }}
{{ '}' }}
// Setup middleware chain
const auth = new AuthMiddleware();
const logging = new LoggingMiddleware();
const validation = new ValidationMiddleware();
auth.setNext(logging).setNext(validation);
// Utilizzo
const request: Request = {{ '{' }}
url: '/api/users',
method: 'POST',
headers: {{ '{' }} authorization: 'Bearer token123' {{ '}' }},
body: {{ '{' }} name: 'Alice' {{ '}' }}
{{ '}' }};
auth.handle(request, () => console.log("🎯 Request processed!"));
// Output:
// 🔐 Checking authentication...
// ✅ Authenticated
// 📝 POST /api/users
// ✔️ Validating request...
// ✅ Validation passed
// 🎯 Request processed!
テンプレートメソッドパターン
Il テンプレートメソッド 基本クラスでアルゴリズムのスケルトンを定義します。 いくつかのステップをサブクラスに委任します。サブクラスは、特定のステップを再定義しません。 全体の構造を変える。
abstract class DataProcessor {{ '{' }}
// Template method: definisce l'algoritmo
public process(): void {{ '{' }}
this.readData();
this.processData();
this.saveData();
this.sendNotification(); // Hook opzionale
{{ '}' }}
// Passi astratti (da implementare)
protected abstract readData(): void;
protected abstract processData(): void;
protected abstract saveData(): void;
// Hook: metodo con implementazione di default
protected sendNotification(): void {{ '{' }}
// Default: non fa nulla (le sottoclassi possono override)
{{ '}' }}
{{ '}' }}
// Concrete implementation
class CSVProcessor extends DataProcessor {{ '{' }}
protected readData(): void {{ '{' }}
console.log("📄 Reading CSV file...");
{{ '}' }}
protected processData(): void {{ '{' }}
console.log("🔄 Parsing CSV rows...");
{{ '}' }}
protected saveData(): void {{ '{' }}
console.log("💾 Saving to database...");
{{ '}' }}
protected sendNotification(): void {{ '{' }}
console.log("📧 Email notification sent");
{{ '}' }}
{{ '}' }}
class JSONProcessor extends DataProcessor {{ '{' }}
protected readData(): void {{ '{' }}
console.log("📄 Reading JSON file...");
{{ '}' }}
protected processData(): void {{ '{' }}
console.log("🔄 Parsing JSON objects...");
{{ '}' }}
protected saveData(): void {{ '{' }}
console.log("💾 Saving to cache...");
{{ '}' }}
// Non override sendNotification (usa default)
{{ '}' }}
// Utilizzo
const csvProcessor = new CSVProcessor();
csvProcessor.process();
// 📄 Reading CSV file...
// 🔄 Parsing CSV rows...
// 💾 Saving to database...
// 📧 Email notification sent
const jsonProcessor = new JSONProcessor();
jsonProcessor.process();
// 📄 Reading JSON file...
// 🔄 Parsing JSON objects...
// 💾 Saving to cache...
// (nessuna notifica)
高度なテンプレート手法: テスト フレームワーク
現実的な例: セットアップ/ティアダウンを備えた自動テスト フレームワーク:
abstract class TestCase {{ '{' }}
// Template method
public run(): void {{ '{' }}
this.setUp();
try {{ '{' }}
this.runTest();
console.log("✅ Test passed");
{{ '}' }} catch (error) {{ '{' }}
console.log(`❌ Test failed: ${{ '{' }}error{{ '}' }}`);
{{ '}' }} finally {{ '{' }}
this.tearDown();
{{ '}' }}
{{ '}' }}
// Hook methods
protected setUp(): void {{ '{' }}
console.log("🔧 Setting up test...");
{{ '}' }}
protected tearDown(): void {{ '{' }}
console.log("🧹 Cleaning up...");
{{ '}' }}
// Passo astratto: ogni test definisce il proprio
protected abstract runTest(): void;
{{ '}' }}
// Test concreti
class DatabaseTest extends TestCase {{ '{' }}
private connection: any;
protected setUp(): void {{ '{' }}
super.setUp();
console.log("🔌 Connecting to test database...");
this.connection = {{ '{' }} connected: true {{ '}' }};
{{ '}' }}
protected runTest(): void {{ '{' }}
console.log("🧪 Testing database queries...");
if (!this.connection.connected) {{ '{' }}
throw new Error("Connection failed");
{{ '}' }}
{{ '}' }}
protected tearDown(): void {{ '{' }}
console.log("🔌 Closing database connection...");
this.connection = null;
super.tearDown();
{{ '}' }}
{{ '}' }}
class APITest extends TestCase {{ '{' }}
protected setUp(): void {{ '{' }}
super.setUp();
console.log("🌐 Starting mock API server...");
{{ '}' }}
protected runTest(): void {{ '{' }}
console.log("🧪 Testing API endpoints...");
// Test logic here
{{ '}' }}
protected tearDown(): void {{ '{' }}
console.log("🛑 Stopping mock server...");
super.tearDown();
{{ '}' }}
{{ '}' }}
// Utilizzo
new DatabaseTest().run();
// 🔧 Setting up test...
// 🔌 Connecting to test database...
// 🧪 Testing database queries...
// ✅ Test passed
// 🔌 Closing database connection...
// 🧹 Cleaning up...
new APITest().run();
// 🔧 Setting up test...
// 🌐 Starting mock API server...
// 🧪 Testing API endpoints...
// ✅ Test passed
// 🛑 Stopping mock server...
// 🧹 Cleaning up...
各パターンをいつ使用するか
✅ 次の場合にコマンドを使用します。
- 便利です 元に戻す/やり直す アプリケーションで
- あなたが欲しいのは mettere in coda オペレーション (ジョブキュー)
- オブジェクトをアクションでパラメータ化する必要がある
- ロギングとトランザクション: 実行されたコマンドを記録します。
✅ 次の場合に責任連鎖を使用します。
- 複数のオブジェクトがリクエストを処理できますが、 どっちか分からない
- あなたが欲しいのは ミドルウェア/パイプライン 逐次処理用
- ハンドラーのセットは動的に変更可能
- イベント管理、ロギング、認証
✅ 次の場合にテンプレート メソッドを使用します。
- アルゴリズムがあります 構造は共通だがステップが異なる
- 同様のコードの重複を避けたい
- サブクラスがオーバーライドできるステップを制御する必要がある
- フレームワーク: スケルトンを定義し、ユーザーが詳細を実装します
実践例: 決済システム
// COMMAND: Transazioni con undo
interface PaymentCommand {{ '{' }}
execute(): void;
undo(): void;
{{ '}' }}
class ChargeCommand implements PaymentCommand {{ '{' }}
constructor(
private account: string,
private amount: number
) {{ '{' }}{{ '}' }}
execute(): void {{ '{' }}
console.log(`💳 Charged ${{ '{' }}this.amount{{ '}' }} to ${{ '{' }}this.account{{ '}' }}`);
{{ '}' }}
undo(): void {{ '{' }}
console.log(`↩️ Refunded ${{ '{' }}this.amount{{ '}' }} to ${{ '{' }}this.account{{ '}' }}`);
{{ '}' }}
{{ '}' }}
// CHAIN: Validazione pagamento
abstract class PaymentHandler {{ '{' }}
protected next: PaymentHandler | null = null;
setNext(handler: PaymentHandler): PaymentHandler {{ '{' }}
this.next = handler;
return handler;
{{ '}' }}
handle(amount: number): boolean {{ '{' }}
if (this.validate(amount)) {{ '{' }}
return this.next ? this.next.handle(amount) : true;
{{ '}' }}
return false;
{{ '}' }}
protected abstract validate(amount: number): boolean;
{{ '}' }}
class AmountValidator extends PaymentHandler {{ '{' }}
protected validate(amount: number): boolean {{ '{' }}
if (amount > 0) {{ '{' }}
console.log("✅ Amount valid");
return true;
{{ '}' }}
console.log("❌ Invalid amount");
return false;
{{ '}' }}
{{ '}' }}
class FraudDetector extends PaymentHandler {{ '{' }}
protected validate(amount: number): boolean {{ '{' }}
if (amount < 10000) {{ '{' }}
console.log("✅ No fraud detected");
return true;
{{ '}' }}
console.log("🚨 Potential fraud");
return false;
{{ '}' }}
{{ '}' }}
// TEMPLATE METHOD: Processamento pagamento
abstract class PaymentProcessor {{ '{' }}
public processPayment(amount: number): void {{ '{' }}
if (!this.validatePayment(amount)) return;
this.deductAmount(amount);
this.sendReceipt();
{{ '}' }}
protected abstract validatePayment(amount: number): boolean;
protected abstract deductAmount(amount: number): void;
protected sendReceipt(): void {{ '{' }}
console.log("📧 Receipt sent");
{{ '}' }}
{{ '}' }}
class CreditCardProcessor extends PaymentProcessor {{ '{' }}
private validator = new AmountValidator();
constructor() {{ '{' }}
super();
this.validator.setNext(new FraudDetector());
{{ '}' }}
protected validatePayment(amount: number): boolean {{ '{' }}
return this.validator.handle(amount);
{{ '}' }}
protected deductAmount(amount: number): void {{ '{' }}
console.log(`💳 Card charged ${{ '{' }}amount{{ '}' }}`);
{{ '}' }}
{{ '}' }}
// Utilizzo combinato
const processor = new CreditCardProcessor();
processor.processPayment(500);
// ✅ Amount valid
// ✅ No fraud detected
// 💳 Card charged 500
// 📧 Receipt sent
結論
これら 3 つの行動パターンは、異なるが補完的な問題を解決します。 指示 元に戻す/やり直しとキューイングのアクションをカプセル化します。 の連鎖 責任 柔軟なハンドラー パイプラインを作成します。 テンプレートメソッド 再利用可能なアルゴリズムにより重複を削減します。それらを組み合わせることでエレガントな建築が生まれます そしてメンテナンス可能。
🎯 重要なポイント
- コマンド = オブジェクトとしてのリクエスト、元に戻す/やり直し、ジョブ キュー
- 責任の連鎖 = ハンドラー、ミドルウェアの連鎖
- テンプレートメソッド = スケルトンアルゴリズム、フックポイント
- 取り消し可能な操作にはコマンドを使用する
- パイプラインの処理にチェーンを使用する
- テンプレートメソッドを使用して重複を避ける







