Command, Chain of Responsibility e Template Method
I pattern comportamentali gestiscono la comunicazione e le responsabilità tra oggetti. Command incapsula richieste come oggetti (undo/redo), Chain of Responsibility passa richieste lungo una catena di handler (middleware), e Template Method definisce scheletri di algoritmi con passi personalizzabili.
🎯 Cosa Imparerai
- Command pattern: incapsulare azioni come oggetti
- Implementare undo/redo con Command
- Chain of Responsibility: catena di handler
- Template Method: algoritmi con hook points
Command Pattern
Il Command pattern incapsula una richiesta come un oggetto, permettendo di parametrizzare metodi con operazioni, mettere in coda richieste, e supportare operazioni annullabili (undo/redo).
// 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)
Command con Undo/Redo Completo
Implementazione avanzata con undo/redo multipli:
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"
Chain of Responsibility Pattern
Il Chain of Responsibility permette a più oggetti di gestire una richiesta senza che il mittente conosca quale la gestirà. La richiesta passa lungo una catena finché qualcuno la gestisce.
// 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"
Chain of Responsibility: Middleware HTTP
Esempio pratico: pipeline di middleware per gestire richieste 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!
Template Method Pattern
Il Template Method definisce lo scheletro di un algoritmo in una classe base, delegando alcuni passi alle sottoclassi. Le sottoclassi ridefiniscono certi passi senza cambiare la struttura complessiva.
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)
Template Method Avanzato: Test Framework
Esempio realistico: framework per test automatizzati con setup/teardown:
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...
Quando Usare Ogni Pattern
✅ Usa Command quando:
- Serve undo/redo nelle applicazioni
- Vuoi mettere in coda operazioni (job queue)
- Serve parametrizzare oggetti con azioni
- Logging e transazioni: registrare comandi eseguiti
✅ Usa Chain of Responsibility quando:
- Più oggetti possono gestire una richiesta, ma non sai quale
- Vuoi middleware/pipeline per processing sequenziale
- Set di handler può cambiare dinamicamente
- Gestione eventi, logging, autenticazione
✅ Usa Template Method quando:
- Hai algoritmi con struttura comune ma passi diversi
- Vuoi evitare duplicazione di codice simile
- Serve controllo su quali passi le sottoclassi possono override
- Framework: definisci scheletro, utenti implementano dettagli
Esempio Pratico: Sistema di Pagamento
// 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
Conclusione
Questi tre pattern comportamentali risolvono problemi diversi ma complementari. Command incapsula azioni per undo/redo e queuing, Chain of Responsibility crea pipeline flessibili di handler, e Template Method riduce duplicazione con algoritmi riutilizzabili. Combinarli crea architetture eleganti e manutenibili.
🎯 Punti Chiave
- Command = richieste come oggetti, undo/redo, job queues
- Chain of Responsibility = catena di handler, middleware
- Template Method = scheletro algoritmo, hook points
- Usa Command per operazioni annullabili
- Usa Chain per pipeline di processing
- Usa Template Method per evitare duplicazione
Domande frequenti
Qual è il principale scopo del Command pattern?
Il Command pattern incapsula una richiesta come un oggetto, permettendo di parametrizzare metodi con operazioni, mettere in coda richieste e supportare operazioni annullabili (undo/redo).
Come funziona il Chain of Responsibility per gestire le richieste?
Il Chain of Responsibility passa la richiesta lungo una catena di handler finché qualcuno la gestisce, senza che il mittente conosca quale lo farà.
Qual è l'obiettivo del Template Method pattern?
Il Template Method definisce lo scheletro di un algoritmo in una classe base, delegando alcuni passi alle sottoclassi per ridurre la duplicazione e creare algoritmi riutilizzabili.
Perché è importante combinare questi tre pattern comportamentali?
Combinarli crea architetture eleganti e manutenibili, risolvendo problemi diversi ma complementari come l'incapsulamento di azioni per undo/redo, la creazione di pipeline flessibili di handler e la riduzione della duplicazione con algoritmi riutilizzabili.

Commenti
Caricamento commenti...
Accedi per commentare