1. Tổng quan
5 pattern còn lại — phổ biến nhất là Strategy và Template Method. State thường được nhầm với Strategy. Visitor và Interpreter dùng ít hơn nhưng cực mạnh trong domain phù hợp (compiler, AST, DSL).
| Pattern | Một câu |
|---|---|
| State | Object đổi hành vi khi state đổi — như đổi class |
| Strategy | Tách thuật toán thành object thay thế được |
| Template Method | Định nghĩa skeleton, để subclass điền chi tiết |
| Visitor | Tách operation khỏi cấu trúc object |
| Interpreter | Định nghĩa grammar + interpreter cho ngôn ngữ nhỏ |
2. State
State
Cho phép object thay đổi hành vi khi state nội bộ thay đổi. Như đổi class runtime.
2.1. Vấn đề: switch hell
// ❌ Mọi method có switch
class Order {
private status: 'pending' | 'paid' | 'shipped' | 'delivered' | 'cancelled' = 'pending';
pay() {
if (this.status === 'pending') this.status = 'paid';
else throw new Error('Cannot pay in status ' + this.status);
}
ship() {
if (this.status === 'paid') this.status = 'shipped';
else throw new Error('Cannot ship in status ' + this.status);
}
deliver() {
if (this.status === 'shipped') this.status = 'delivered';
else throw new Error('Cannot deliver in status ' + this.status);
}
cancel() {
if (this.status === 'shipped' || this.status === 'delivered')
throw new Error('Cannot cancel');
this.status = 'cancelled';
}
}
2.2. State pattern
// State interface
interface OrderState {
pay(order: Order): void;
ship(order: Order): void;
deliver(order: Order): void;
cancel(order: Order): void;
}
class Order {
state: OrderState = new PendingState();
setState(s: OrderState) { this.state = s; }
pay() { this.state.pay(this); }
ship() { this.state.ship(this); }
deliver() { this.state.deliver(this); }
cancel() { this.state.cancel(this); }
}
class PendingState implements OrderState {
pay(order: Order) { order.setState(new PaidState()); console.log('Paid'); }
ship(order: Order) { throw new Error('Cannot ship unpaid'); }
deliver(order: Order) { throw new Error('Cannot deliver unpaid'); }
cancel(order: Order) { order.setState(new CancelledState()); }
}
class PaidState implements OrderState {
pay(order: Order) { throw new Error('Already paid'); }
ship(order: Order) { order.setState(new ShippedState()); console.log('Shipped'); }
deliver(order: Order) { throw new Error('Cannot deliver before ship'); }
cancel(order: Order) { order.setState(new CancelledState()); /* refund */ }
}
class ShippedState implements OrderState {
pay(order: Order) { throw new Error('Already paid'); }
ship(order: Order) { throw new Error('Already shipped'); }
deliver(order: Order) { order.setState(new DeliveredState()); console.log('Delivered'); }
cancel(order: Order) { throw new Error('Cannot cancel shipped'); }
}
class DeliveredState implements OrderState {
pay(order: Order) { throw new Error('Already done'); }
ship(order: Order) { throw new Error('Already done'); }
deliver(order: Order) { throw new Error('Already delivered'); }
cancel(order: Order) { throw new Error('Cannot cancel delivered'); }
}
class CancelledState implements OrderState {
pay(order: Order) { throw new Error('Cancelled'); }
ship(order: Order) { throw new Error('Cancelled'); }
deliver(order: Order) { throw new Error('Cancelled'); }
cancel(order: Order) { /* no-op */ }
}
Mỗi state là class riêng, tự biết chuyển sang state nào. Không có switch khắp nơi.
2.3. Use cases
- Order/booking workflow.
- TCP connection: LISTEN → ESTABLISHED → CLOSED.
- Game character: Idle → Walking → Running → Jumping.
- Document workflow: Draft → Review → Published → Archived.
- Player media: Playing → Paused → Stopped.
2.4. Alternative: state machine library
Cho FSM phức tạp, dùng XState (TS), Stateless (C#) — declarative, có visualization, side-effect quản lý tốt hơn.
// XState style:
const orderMachine = createMachine({
id: 'order',
initial: 'pending',
states: {
pending: { on: { PAY: 'paid', CANCEL: 'cancelled' } },
paid: { on: { SHIP: 'shipped', CANCEL: 'cancelled' } },
shipped: { on: { DELIVER: 'delivered' } },
delivered: { type: 'final' },
cancelled: { type: 'final' },
},
});
3. Strategy
Strategy
Định nghĩa family of algorithm, đóng gói mỗi cái thành object, làm chúng thay thế lẫn nhau.
3.1. Implementation
// Strategy interface
interface PricingStrategy {
calculate(order: Order): Money;
}
// Concrete strategies
class StandardPricing implements PricingStrategy {
calculate(o: Order) { return o.subtotal(); }
}
class VipDiscountPricing implements PricingStrategy {
calculate(o: Order) {
return o.customer.isVip() ? o.subtotal().multiply(0.85) : o.subtotal();
}
}
class HolidayPromoPricing implements PricingStrategy {
calculate(o: Order) {
const isHoliday = new Date().getMonth() === 11;
return isHoliday ? o.subtotal().subtract(Money.of(10)) : o.subtotal();
}
}
class ClearanceSalePricing implements PricingStrategy {
calculate(o: Order) { return o.subtotal().multiply(0.5); }
}
// Context
class Checkout {
constructor(private pricing: PricingStrategy) {}
setPricing(p: PricingStrategy) { this.pricing = p; } // có thể đổi runtime
finalize(order: Order): Money {
return this.pricing.calculate(order);
}
}
// Sử dụng:
const checkout = new Checkout(new StandardPricing());
console.log(checkout.finalize(order));
// Black Friday → đổi strategy
checkout.setPricing(new ClearanceSalePricing());
console.log(checkout.finalize(order));
3.2. Function as Strategy (idiomatic JS/TS)
Strategy thường chỉ là 1 function. Class chỉ cần khi strategy có state hoặc method nhiều.
type PricingFn = (order: Order) => Money;
const standardPricing: PricingFn = o => o.subtotal();
const vipPricing: PricingFn = o => o.customer.isVip() ? o.subtotal().multiply(0.85) : o.subtotal();
const clearancePricing: PricingFn = o => o.subtotal().multiply(0.5);
class Checkout {
constructor(private pricing: PricingFn) {}
finalize(o: Order) { return this.pricing(o); }
}
3.3. Use cases mạnh
- Sort:
Array.sort(compareFn)— compareFn là Strategy. - Auth: BasicAuth, OAuth, JWT — same interface.
- Compression: gzip, brotli, deflate.
- Payment: Stripe, PayPal, VNPay.
- Validation: rule chain.
- Routing: shortest path, bus, walking.
4. State vs Strategy — phân biệt
2 pattern có cấu trúc giống hệt (Context có field reference đến interface, swap được). Khác ở intent:
State
- State biểu diễn tình trạng của object.
- Object tự đổi state internally (state này transition sang state khác).
- Client thường KHÔNG chọn state — workflow tự dẫn.
- State biết về context, có thể đổi context.state.
- Số state hữu hạn và cố định.
Strategy
- Strategy là thuật toán để giải 1 việc.
- Client chọn strategy, set vào context.
- Strategy không biết các strategy khác.
- Có thể có vô hạn strategy mới — dễ extension.
- Strategy không tự đổi (context không tự switch).
Câu hỏi tự kiểm tra: "Object có tự đổi 'class' của mình không?"
- Có (Order pending → paid → shipped) → State.
- Không (PricingEngine có 1 strategy do client set) → Strategy.
5. Template Method
Template Method
Định nghĩa skeleton thuật toán trong method của parent, để subclass override một số bước cụ thể mà không thay đổi cấu trúc tổng.
5.1. Implementation
abstract class DataExporter {
// Template method — final, không cho override
export(data: any[]): string {
this.validate(data);
const opened = this.openDocument();
const written = this.writeRows(opened, data);
return this.closeDocument(written);
}
protected validate(data: any[]) {
if (!Array.isArray(data)) throw new Error('invalid');
}
// Steps abstract — subclass implement
protected abstract openDocument(): string;
protected abstract writeRows(doc: string, data: any[]): string;
protected abstract closeDocument(doc: string): string;
}
class CsvExporter extends DataExporter {
protected openDocument() { return ''; }
protected writeRows(doc: string, data: any[]) {
return data.map(r => Object.values(r).join(',')).join('\n');
}
protected closeDocument(doc: string) { return doc; }
}
class JsonExporter extends DataExporter {
protected openDocument() { return '['; }
protected writeRows(doc: string, data: any[]) {
return doc + data.map(r => JSON.stringify(r)).join(',');
}
protected closeDocument(doc: string) { return doc + ']'; }
}
class XmlExporter extends DataExporter {
protected openDocument() { return '<?xml version="1.0"?>\n<data>\n'; }
protected writeRows(doc: string, data: any[]) {
return doc + data.map(r =>
' <row>' + Object.entries(r).map(([k, v]) => `<${k}>${v}</${k}>`).join('') + '</row>'
).join('\n');
}
protected closeDocument(doc: string) { return doc + '\n</data>'; }
}
5.2. Hook methods
Bước có default implementation rỗng, subclass có thể override nếu cần:
abstract class DataExporter {
export(data: any[]): string {
this.beforeExport(data); // hook
/* ... */
this.afterExport(); // hook
return result;
}
// Hooks với default rỗng
protected beforeExport(data: any[]) {}
protected afterExport() {}
}
class CsvWithLogging extends CsvExporter {
protected beforeExport(data: any[]) { console.log(`Exporting ${data.length} rows`); }
}
5.3. Template Method vs Strategy
| Template Method | Strategy | |
|---|---|---|
| Variation | Inheritance — subclass override step | Composition — swap object |
| Coupling | Compile-time (static) | Runtime (dynamic) |
| Reuse code chung | Trong parent class | Phải duplicate hoặc abstract khác |
| Khi nào dùng | Skeleton cố định, vài step khác nhau | Toàn bộ thuật toán khác, swap được |
Quy tắc: nếu chỉ 1-2 bước khác nhau giữa biến thể → Template Method. Nếu cả thuật toán khác → Strategy.
5.4. Use cases
- Framework hook: React class component
componentDidMount,render. - Spring
JdbcTemplate: define connection/transaction skeleton, callback override SQL. - Build pipeline: skeleton (validate → compile → test → deploy), step override theo project type.
- Game AI: skeleton tick (sense → think → act), child class override think logic.
6. Visitor
Visitor
Tách operation khỏi cấu trúc object. Cho phép định nghĩa operation mới trên hierarchy mà không sửa class trong hierarchy.
6.1. Vấn đề: thêm operation
Ngôn ngữ tính số đơn giản với AST: Number, Add, Multiply. Operation cần làm: evaluate, print, optimize.
// ❌ Mỗi operation phải sửa mọi class:
abstract class Expr {
abstract evaluate(): number;
abstract print(): string;
abstract optimize(): Expr;
// Thêm operation mới (lint?) → sửa mọi class
}
6.2. Visitor pattern
// Visitor interface
interface ExprVisitor<R> {
visitNumber(e: NumberExpr): R;
visitAdd(e: AddExpr): R;
visitMul(e: MulExpr): R;
}
// Element interface
abstract class Expr {
abstract accept<R>(visitor: ExprVisitor<R>): R;
}
// Concrete elements
class NumberExpr extends Expr {
constructor(public value: number) { super(); }
accept<R>(v: ExprVisitor<R>) { return v.visitNumber(this); }
}
class AddExpr extends Expr {
constructor(public left: Expr, public right: Expr) { super(); }
accept<R>(v: ExprVisitor<R>) { return v.visitAdd(this); }
}
class MulExpr extends Expr {
constructor(public left: Expr, public right: Expr) { super(); }
accept<R>(v: ExprVisitor<R>) { return v.visitMul(this); }
}
// Operations as visitors
class EvalVisitor implements ExprVisitor<number> {
visitNumber(e: NumberExpr) { return e.value; }
visitAdd(e: AddExpr) { return e.left.accept(this) + e.right.accept(this); }
visitMul(e: MulExpr) { return e.left.accept(this) * e.right.accept(this); }
}
class PrintVisitor implements ExprVisitor<string> {
visitNumber(e: NumberExpr) { return String(e.value); }
visitAdd(e: AddExpr) { return `(${e.left.accept(this)} + ${e.right.accept(this)})`; }
visitMul(e: MulExpr) { return `(${e.left.accept(this)} * ${e.right.accept(this)})`; }
}
// Sử dụng: (2 + 3) * 4
const expr = new MulExpr(new AddExpr(new NumberExpr(2), new NumberExpr(3)), new NumberExpr(4));
console.log(expr.accept(new EvalVisitor())); // 20
console.log(expr.accept(new PrintVisitor())); // ((2 + 3) * 4)
// Thêm operation mới — chỉ thêm Visitor:
class OptimizeVisitor implements ExprVisitor<Expr> {
visitNumber(e: NumberExpr) { return e; }
visitAdd(e: AddExpr) {
const l = e.left.accept(this), r = e.right.accept(this);
if (l instanceof NumberExpr && r instanceof NumberExpr) return new NumberExpr(l.value + r.value);
return new AddExpr(l, r);
}
visitMul(e: MulExpr) { /* ... */ return e; }
}
6.3. Double dispatch
Visitor giải quyết "double dispatch" — chọn method dựa trên 2 type (visitor + element). Single dispatch chỉ chọn theo 1 type (the method's class). element.accept(visitor) dispatch theo element type, rồi visitor.visitX dispatch theo visitor type.
6.4. Use cases
- Compiler/AST: linter, formatter, optimizer, code gen — mỗi cái 1 visitor.
- File system: backup, virus scan, permission check.
- UI tree: render, layout, hit-test.
- Document: spell-check, word-count, export.
TypeScript compiler, Babel, ESLint, Prettier — đều dùng Visitor (hoặc biến thể).
6.5. Khi KHÔNG dùng
- Hierarchy thay đổi nhiều (thêm node type) — Visitor không OCP với chiều này.
- Operation đơn giản, ít — method trên class đủ.
7. Interpreter
Interpreter
Định nghĩa biểu diễn cho grammar của ngôn ngữ + interpreter dùng biểu diễn để diễn dịch câu.
7.1. Implementation: simple boolean DSL
Hỗ trợ true, false, and, or, not, variable.
interface Expr {
evaluate(ctx: Record<string, boolean>): boolean;
}
class TrueLit implements Expr {
evaluate() { return true; }
}
class FalseLit implements Expr {
evaluate() { return false; }
}
class Variable implements Expr {
constructor(public name: string) {}
evaluate(ctx: Record<string, boolean>) { return ctx[this.name] ?? false; }
}
class And implements Expr {
constructor(public left: Expr, public right: Expr) {}
evaluate(ctx: any) { return this.left.evaluate(ctx) && this.right.evaluate(ctx); }
}
class Or implements Expr {
constructor(public left: Expr, public right: Expr) {}
evaluate(ctx: any) { return this.left.evaluate(ctx) || this.right.evaluate(ctx); }
}
class Not implements Expr {
constructor(public expr: Expr) {}
evaluate(ctx: any) { return !this.expr.evaluate(ctx); }
}
// "isAdmin AND (isLoggedIn OR isVip)"
const expr = new And(
new Variable('isAdmin'),
new Or(new Variable('isLoggedIn'), new Variable('isVip')),
);
console.log(expr.evaluate({ isAdmin: true, isLoggedIn: false, isVip: true })); // true
console.log(expr.evaluate({ isAdmin: false, isLoggedIn: true, isVip: true })); // false
7.2. Use cases
- Rule engine: feature flag DSL, permission rule.
- Query DSL: MongoDB query, JQ.
- Math expression evaluator.
- Template engine.
7.3. Khi KHÔNG dùng
Grammar phức tạp → dùng parser generator (ANTLR, PEG.js) — Interpreter pattern không scale.
8. Bảng tổng hợp 23 GoF
8.1. Creational (5)
| # | Tên | Khi dùng (nhanh) |
|---|---|---|
| 1 | Factory Method | Subclass quyết định class instantiate |
| 2 | Abstract Factory | Family object cùng style/platform |
| 3 | Builder | Object phức tạp với nhiều tham số |
| 4 | Prototype | Clone template thay vì tạo từ đầu |
| 5 | Singleton | 1 instance toàn app (cẩn thận lạm dụng) |
8.2. Structural (7)
| 6 | Adapter | Tương thích interface khác |
| 7 | Bridge | Tách abstraction × implementation |
| 8 | Composite | Cây leaf + composite cùng interface |
| 9 | Decorator | Thêm chức năng động |
| 10 | Facade | Đơn giản hóa subsystem |
| 11 | Flyweight | Chia sẻ object — giảm memory |
| 12 | Proxy | Kiểm soát truy cập (lazy/cache/auth) |
8.3. Behavioral (11)
| 13 | Chain of Responsibility | Chuỗi handler (middleware) |
| 14 | Command | Action như object (undo/queue) |
| 15 | Iterator | Duyệt collection thống nhất |
| 16 | Mediator | N-N qua trung gian |
| 17 | Memento | Snapshot state |
| 18 | Observer | Pub/Sub 1-N |
| 19 | State | Object đổi hành vi theo state |
| 20 | Strategy | Family thuật toán thay thế |
| 21 | Template Method | Skeleton + step override |
| 22 | Visitor | Tách operation khỏi hierarchy |
| 23 | Interpreter | Grammar cho DSL nhỏ |
9. Bài tập
- State: implement TCP connection state machine với 4 state: CLOSED → LISTEN → ESTABLISHED → CLOSED. Method: open(), accept(), send(), close().
- Strategy: implement compression engine với 3 strategy: Gzip, Brotli, NoCompression. Context có method
compress(data). - Phân biệt: code dưới dùng pattern nào — State hay Strategy?
Sửa nó thành State pattern (object tự transition).class Player { private playback: PlaybackBehavior; setPlayback(p: PlaybackBehavior) { this.playback = p; } play() { this.playback.play(); } } - Template Method: implement
HotDrinkvới template "boil water → brew → pour → addCondiments". Subclass Tea, Coffee override brew và addCondiments. - Visitor: AST cho ngôn ngữ tính số (Number, Add, Mul). Implement Eval và Print visitor như mục 6.2. Thêm Optimize visitor (folding constants).
- Interpreter: parse + evaluate biểu thức boolean như "isAdmin AND (isLoggedIn OR isVip)" từ string. (Tip: dùng split đơn giản, không cần parser thật.)
- Liệt kê 3 pattern bạn nghĩ gặp nhiều nhất trong code hằng ngày, kèm ví dụ.
10. Quiz
Quiz cuối Chương 7
State vs Strategy khác nhau chính ở:
Strategy trong JS/TS có thể đơn giản hóa thành:
Array.sort(compareFn) dùng Strategy as function. .filter, .map nhận function strategy. Class Strategy chỉ cần khi có state hoặc nhiều method liên quan.Template Method dùng:
Visitor giải quyết vấn đề:
"Double dispatch" trong Visitor nghĩa là:
Interpreter pattern phù hợp khi:
XState hoặc state machine library thay thế cho:
Pattern phổ biến NHẤT trong code hàng ngày (web dev modern):
Hoàn thành Chương 7 — toàn bộ 23 GoF patterns. Tiếp theo: Chương 8 — Anti-patterns + Refactoring + DDD-lite →