1. Tổng quan Behavioral Patterns
Nhóm này tập trung vào cách các object giao tiếp với nhau và phân chia trách nhiệm. Khác Structural (đóng/ghép cấu trúc), Behavioral nói về luồng điều khiển và message giữa object.
11 pattern chia 2 chương. Phần 1 — 6 pattern thường gặp:
| Pattern | Một câu |
|---|---|
| Chain of Responsibility | Chuỗi handler, mỗi cái xử lý hoặc pass tiếp |
| Command | Đóng gói request thành object |
| Iterator | Duyệt collection mà không lộ cấu trúc |
| Mediator | 1 object trung gian giảm coupling N-N giữa nhiều object |
| Memento | Lưu/phục hồi state mà không phá encapsulation |
| Observer | 1-N publish/subscribe |
2. Chain of Responsibility
Chain of Responsibility
Tránh coupling sender với receiver bằng cách cho nhiều object cơ hội xử lý request. Pass request dọc chuỗi cho đến khi có handler xử lý.
2.1. Implementation
interface Request {
type: 'auth' | 'cors' | 'rate-limit' | 'data';
user?: { id: string; role: string };
body?: any;
}
abstract class Handler {
protected next: Handler | null = null;
setNext(h: Handler): Handler {
this.next = h;
return h; // cho phép chain: a.setNext(b).setNext(c)
}
handle(req: Request): any {
if (this.next) return this.next.handle(req);
return null; // cuối chuỗi
}
}
class AuthHandler extends Handler {
handle(req: Request) {
if (!req.user) throw new Error('401 Unauthorized');
return super.handle(req);
}
}
class RoleHandler extends Handler {
constructor(private requiredRole: string) { super(); }
handle(req: Request) {
if (req.user!.role !== this.requiredRole) throw new Error('403 Forbidden');
return super.handle(req);
}
}
class RateLimitHandler extends Handler {
private hits = new Map<string, number>();
handle(req: Request) {
const userId = req.user!.id;
const count = (this.hits.get(userId) ?? 0) + 1;
this.hits.set(userId, count);
if (count > 100) throw new Error('429 Too Many');
return super.handle(req);
}
}
class BusinessHandler extends Handler {
handle(req: Request) {
return { message: `Processed ${req.type}` };
}
}
// Setup chuỗi:
const auth = new AuthHandler();
auth.setNext(new RoleHandler('admin'))
.setNext(new RateLimitHandler())
.setNext(new BusinessHandler());
auth.handle({
type: 'data',
user: { id: 'u1', role: 'admin' },
});
2.2. Use cases thực tế
- Express middleware:
app.use(auth).use(cors).use(rateLimit).use(handler)— đây là Chain of Responsibility. - Event bubbling DOM — event đi từ child đến parent đến document.
- Approval workflow: low-level → manager → director → CEO tùy giá trị.
- Logging level chain: DEBUG → INFO → WARN → ERROR.
- Try-catch chain trong nhiều ngôn ngữ.
3. Command
Command
Đóng gói request thành object — cho phép parameterize client, queue request, log, undo.
3.1. Implementation cơ bản
interface Command {
execute(): void;
undo(): void;
}
class TextEditor {
private content = '';
insert(text: string, pos: number) {
this.content = this.content.slice(0, pos) + text + this.content.slice(pos);
}
delete(pos: number, length: number) {
this.content = this.content.slice(0, pos) + this.content.slice(pos + length);
}
getContent() { return this.content; }
}
class InsertCommand implements Command {
constructor(
private editor: TextEditor,
private text: string,
private pos: number,
) {}
execute() { this.editor.insert(this.text, this.pos); }
undo() { this.editor.delete(this.pos, this.text.length); }
}
class DeleteCommand implements Command {
private deleted = '';
constructor(
private editor: TextEditor,
private pos: number,
private length: number,
) {}
execute() {
this.deleted = this.editor.getContent().slice(this.pos, this.pos + this.length);
this.editor.delete(this.pos, this.length);
}
undo() { this.editor.insert(this.deleted, this.pos); }
}
// Invoker với undo/redo:
class CommandHistory {
private undoStack: Command[] = [];
private redoStack: Command[] = [];
execute(cmd: Command) {
cmd.execute();
this.undoStack.push(cmd);
this.redoStack = []; // clear redo
}
undo() {
const cmd = this.undoStack.pop();
if (cmd) {
cmd.undo();
this.redoStack.push(cmd);
}
}
redo() {
const cmd = this.redoStack.pop();
if (cmd) {
cmd.execute();
this.undoStack.push(cmd);
}
}
}
// Sử dụng:
const editor = new TextEditor();
const history = new CommandHistory();
history.execute(new InsertCommand(editor, 'Hello', 0));
history.execute(new InsertCommand(editor, ' World', 5));
console.log(editor.getContent()); // "Hello World"
history.undo();
console.log(editor.getContent()); // "Hello"
history.redo();
console.log(editor.getContent()); // "Hello World"
3.2. Use cases mạnh
- Text editor với undo/redo (Photoshop, IDE).
- Job queue (Redis BullMQ, AWS SQS).
- Macro recording / replay.
- Transaction trong DB (commit/rollback).
- Wizard form: gom các change thành 1 transaction.
- Distributed system: gửi command qua message broker.
3.3. Functional alternative
Command = "function as object". Trong FP-style, function tự nó là Command:
type Command = { do: () => void; undo: () => void };
function insertCmd(editor: TextEditor, text: string, pos: number): Command {
return {
do: () => editor.insert(text, pos),
undo: () => editor.delete(pos, text.length),
};
}
Object literal với 2 closure đủ — không cần class. Đơn giản hơn cho TS/JS.
4. Iterator
Iterator
Cho cách tuần tự duyệt phần tử của collection mà không lộ cấu trúc bên trong.
4.1. Iterator chuẩn (manual)
interface Iterator<T> {
next(): { value: T; done: boolean };
hasNext(): boolean;
}
interface Iterable<T> {
iterator(): Iterator<T>;
}
// Cây nhị phân
class TreeNode<T> {
constructor(public value: T, public left: TreeNode<T> | null = null, public right: TreeNode<T> | null = null) {}
}
// In-order iterator
class InOrderIterator<T> implements Iterator<T> {
private stack: TreeNode<T>[] = [];
constructor(root: TreeNode<T> | null) { this.pushLeft(root); }
private pushLeft(node: TreeNode<T> | null) {
while (node) { this.stack.push(node); node = node.left; }
}
hasNext() { return this.stack.length > 0; }
next() {
if (!this.hasNext()) return { value: null as any, done: true };
const node = this.stack.pop()!;
this.pushLeft(node.right);
return { value: node.value, done: false };
}
}
class BinaryTree<T> implements Iterable<T> {
constructor(public root: TreeNode<T> | null) {}
iterator() { return new InOrderIterator(this.root); }
}
4.2. JavaScript Iterator Protocol — built-in
JS có protocol chuẩn: object có method [Symbol.iterator]() trả về iterator (object có method next()).
class Range implements Iterable<number> {
constructor(private start: number, private end: number, private step = 1) {}
[Symbol.iterator](): Iterator<number> {
let current = this.start;
const { end, step } = this;
return {
next() {
if (current < end) {
const value = current;
current += step;
return { value, done: false };
}
return { value: undefined, done: true };
},
};
}
}
// Dùng with for...of:
for (const n of new Range(0, 5)) console.log(n); // 0, 1, 2, 3, 4
// Spread + destructure:
[...new Range(0, 3)]; // [0, 1, 2]
Array.from(new Range(0, 3)); // [0, 1, 2]
4.3. Generator — đơn giản hóa
class Range {
constructor(private start: number, private end: number) {}
*[Symbol.iterator]() {
for (let i = this.start; i < this.end; i++) yield i;
}
}
// Tree in-order với generator:
function* inOrder<T>(node: TreeNode<T> | null): Generator<T> {
if (!node) return;
yield* inOrder(node.left);
yield node.value;
yield* inOrder(node.right);
}
Generator function* + yield tạo iterator tự nhiên trong JS/TS, Python. Sạch hơn class iterator nhiều.
4.4. Use cases
- Custom collection (tree, graph, linked list).
- Streaming data từ DB / API (paginated cursor).
- Lazy evaluation — generator không tính giá trị đến khi
next(). - Infinite sequence: Fibonacci, primes.
5. Mediator
Mediator
Định nghĩa object đóng gói cách 1 nhóm object giao tiếp. Giảm coupling N-N thành N-1.
5.1. Vấn đề: spaghetti N×N
5.2. Implementation: form validation
// Mediator interface
interface FormMediator {
notify(sender: Component, event: string): void;
}
abstract class Component {
constructor(protected mediator: FormMediator) {}
}
class TextField extends Component {
private _value = '';
set value(v: string) {
this._value = v;
this.mediator.notify(this, 'changed');
}
get value() { return this._value; }
}
class Button extends Component {
private _enabled = true;
click() { this.mediator.notify(this, 'clicked'); }
setEnabled(b: boolean) { this._enabled = b; }
isEnabled() { return this._enabled; }
}
class Checkbox extends Component {
private _checked = false;
toggle() {
this._checked = !this._checked;
this.mediator.notify(this, 'toggled');
}
isChecked() { return this._checked; }
}
// Concrete mediator
class LoginForm implements FormMediator {
email = new TextField(this);
password = new TextField(this);
remember = new Checkbox(this);
submit = new Button(this);
notify(sender: Component, event: string) {
if (sender === this.email || sender === this.password) {
// Logic: enable submit khi đầy đủ
const valid = this.email.value.includes('@') && this.password.value.length >= 6;
this.submit.setEnabled(valid);
}
if (sender === this.submit && event === 'clicked') {
console.log('Login:', this.email.value, this.remember.isChecked() ? '(remember)' : '');
}
}
}
const form = new LoginForm();
form.email.value = 'a@x.com';
form.password.value = 'secret';
form.submit.click();
5.3. Use cases
- UI form: button enable/disable phụ thuộc nhiều input.
- Chat room: User1 không gửi trực tiếp tới User2 — qua ChatRoom mediator.
- Air traffic control — máy bay không nói trực tiếp với nhau, qua tower.
- Air traffic control — máy bay không nói trực tiếp với nhau, qua tower.
- Microservice orchestrator (vs choreography).
6. Memento
Memento
Capture và externalize state nội bộ của object để có thể restore sau, mà không vi phạm encapsulation.
6.1. Implementation
// Memento (opaque snapshot)
class EditorMemento {
// Public field — chỉ Editor đọc
constructor(public readonly state: string) {}
}
// Originator
class Editor {
private content = '';
type(text: string) { this.content += text; }
save(): EditorMemento {
return new EditorMemento(this.content);
}
restore(m: EditorMemento) {
this.content = m.state;
}
getContent() { return this.content; }
}
// Caretaker — quản lý history, không đọc memento content
class History {
private mementos: EditorMemento[] = [];
push(m: EditorMemento) { this.mementos.push(m); }
pop(): EditorMemento | undefined { return this.mementos.pop(); }
}
// Sử dụng:
const editor = new Editor();
const history = new History();
editor.type('Hello');
history.push(editor.save());
editor.type(' World');
console.log(editor.getContent()); // "Hello World"
editor.restore(history.pop()!);
console.log(editor.getContent()); // "Hello"
6.2. Memento vs Command-undo
Hai cách undo:
- Memento: lưu full snapshot trước action. Đơn giản, đắt memory.
- Command undo: lưu delta để inverse action. Tiết kiệm, phức tạp implement.
Trong thực tế: Memento cho object nhỏ (form state), Command cho action có inverse rõ (insert/delete text).
6.3. Use cases
- Game save/load.
- Form draft auto-save.
- Database transaction: snapshot trước commit.
- Undo trong app đơn giản.
7. Observer
Observer
Định nghĩa quan hệ 1-N: khi 1 object đổi state, mọi observer được notify tự động.
7.1. Implementation
type Observer<T> = (value: T) => void;
class Subject<T> {
private observers = new Set<Observer<T>>();
subscribe(obs: Observer<T>): () => void {
this.observers.add(obs);
return () => this.observers.delete(obs); // unsubscribe function
}
notify(value: T) {
for (const obs of this.observers) obs(value);
}
}
// Sử dụng:
const stockPrice = new Subject<number>();
const unsub1 = stockPrice.subscribe(price => console.log('Logger:', price));
const unsub2 = stockPrice.subscribe(price => {
if (price > 100) console.log('Alert: high price!', price);
});
stockPrice.notify(95);
stockPrice.notify(105);
unsub1(); // dừng nhận log
stockPrice.notify(110); // chỉ alert, không log
7.2. Use cases khắp nơi
- Event listener DOM:
button.addEventListener('click', handler)— Observer pattern thuần. - RxJS: Observable + Observer là extension mạnh của pattern này.
- Redux store.subscribe: state thay đổi → re-render UI.
- Vue/React reactivity: dependency tracking.
- Pub/Sub message queue: Kafka, Redis pub/sub.
- Hooks file watch: chokidar, Webpack HMR.
7.3. Push vs Pull
- Push — subject gửi data trong notify():
obs(newValue). Observer dùng ngay. - Pull — subject chỉ "ping", observer tự lấy:
obs(); /* observer.read(subject) */. Linh hoạt hơn nhưng coupling cao hơn.
Đa số implementation hiện đại dùng push.
7.4. Pitfall: Memory leak
Observer giữ reference. Nếu không unsubscribe, subject giữ observer mãi → Observer không bị GC. Phổ biến trong React khi component unmount mà không clean up subscription:
useEffect(() => {
const unsub = store.subscribe(handleChange);
return unsub; // ← clean up khi unmount
}, []);
7.5. Observer vs Mediator
- Observer: 1-N quan hệ subscribe; subject không biết observer cụ thể.
- Mediator: N-N decoupling qua trung gian; mediator biết các thành phần.
8. So sánh nhanh 6 Behavioral Part 1
| Pattern | Quan hệ | Use case mẫu |
|---|---|---|
| Chain of Responsibility | Chain handler | Express middleware |
| Command | Action như object | Undo/redo, queue |
| Iterator | 1 collection | Tree traversal, lazy stream |
| Mediator | N-N qua trung gian | UI form, chat room |
| Memento | Snapshot state | Save/load, undo |
| Observer | 1-N publish | Event listener, reactive |
9. Bài tập
- Chain of Responsibility: implement validation chain cho form đăng ký: NotEmpty → ValidEmail → UniqueEmail → StrongPassword. Mỗi handler trả error hoặc pass tiếp.
- Command: implement remote control TV với button Volume+, Volume-, Mute, Unmute. Hỗ trợ undo cho lần bấm gần nhất.
- Iterator (generator): viết hàm
function* primes(): Generator<number>sinh số nguyên tố vô hạn. Dùngfor...ofvớibreakđể lấy 100 số đầu. - Mediator: chat room với 3 user. User gửi message qua ChatRoom, room broadcast cho user khác. Code không có direct user-to-user reference.
- Memento: Game character có hp, mp, position. Implement save/load state qua Memento. Demo undo move sai.
- Observer: tạo simple Pub/Sub cho stock price. 3 subscriber: Logger, Alert (giá > 100), Chart (đổi UI). Demo unsubscribe.
- So sánh: Observer vs Mediator — cho 2 use case, mỗi use case nói tại sao chọn pattern đó.
10. Quiz
Quiz cuối Chương 6
Chain of Responsibility được dùng nhiều nhất ở đâu trong web dev?
app.use(auth).use(cors).use(rateLimit).use(handler). Mỗi middleware xử lý hoặc next() pass tiếp. DOM event bubbling cũng là biến thể.Command pattern phù hợp nhất khi cần:
JavaScript generator (function*):
yield tạm dừng, lần gọi next() chạy tiếp. Dùng cho infinite sequence (Fibonacci, primes), tree traversal, async iteration. Sạch hơn class Iterator nhiều.Mediator giảm coupling từ:
Memento KHÁC Command-undo ở chỗ:
Observer pattern xuất hiện rõ ràng trong:
Pitfall lớn nhất của Observer:
Observer vs Mediator:
Hoàn thành Chương 6 (18/23 patterns). Tiếp theo: Chương 7 — Behavioral Part 2 (5 mẫu) →