Chương 06 · Behavioral — Part 1

Behavioral Patterns — Phần 1 (6 mẫu)

Chain of Responsibility · Command · Iterator · Mediator · Memento · Observer. Cách object giao tiếp và phân chia trách nhiệm runtime.

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 nhauphâ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:

PatternMột câu
Chain of ResponsibilityChuỗi handler, mỗi cái xử lý hoặc pass tiếp
CommandĐóng gói request thành object
IteratorDuyệt collection mà không lộ cấu trúc
Mediator1 object trung gian giảm coupling N-N giữa nhiều object
MementoLưu/phục hồi state mà không phá encapsulation
Observer1-N publish/subscribe

2. Chain of Responsibility

PATTERN №13 — BEHAVIORAL

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ý.

Vấn đề Có nhiều cách xử lý 1 request, không biết handler nào sẽ xử lý ở compile-time. Lợi ích Decouple sender-receiver. Cấu hình chuỗi runtime. Easy thêm/bớt handler. Đánh đổi Không đảm bảo request được xử lý. Khó debug.

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

PATTERN №14 — BEHAVIORAL

Command

Đóng gói request thành object — cho phép parameterize client, queue request, log, undo.

Vấn đề Cần queue, log, undo, retry, schedule, redo các action. Lợi ích Action thành "first-class citizen" — pass, store, transform. Đánh đổi Tạo thêm class cho mỗi action.

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

PATTERN №15 — BEHAVIORAL

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.

Vấn đề Collection có cấu trúc khác nhau (array, tree, graph, linked list) — client muốn duyệt thống nhất. Lợi ích Client code không phụ thuộc cấu trúc collection. Hỗ trợ nhiều cách duyệt. Đánh đổi Có thể overhead nhỏ với collection đơn giản.

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

PATTERN №16 — BEHAVIORAL

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.

Vấn đề Nhiều object giao tiếp trực tiếp với nhau → coupling N×N. Đổi 1 object phải sửa nhiều. Lợi ích Mỗi object chỉ biết Mediator. Logic tương tác tập trung 1 chỗ. Đánh đổi Mediator có thể trở thành "god object" nếu phình quá.

5.1. Vấn đề: spaghetti N×N

Trước Mediator (N×N coupling): ┌────────────────────────────────────┐ │ Button ────► Dialog │ │ │ ◄──────── │ │ │ │ ▼ │ │ ▼ Checkbox │ │ TextField ◄────► Submit │ │ ▲ │ │ │ └────────────────┘ │ └────────────────────────────────────┘ Sau Mediator (N-1 coupling): ┌─────────────┐ │ Mediator │ └──────┬──────┘ │ ┌─────┬─────┼─────┬─────┐ ▼ ▼ ▼ ▼ ▼ Button Text Submit Check Dialog

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

PATTERN №17 — BEHAVIORAL

Memento

Capture và externalize state nội bộ của object để có thể restore sau, mà không vi phạm encapsulation.

Vấn đề Cần undo/checkpoint nhưng không muốn expose mọi internal field qua getter/setter. Lợi ích Bảo toàn encapsulation. Snapshot opaque cho client. Đánh đổi Nếu state lớn, snapshot tốn memory.

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

PATTERN №18 — BEHAVIORAL

Observer

Định nghĩa quan hệ 1-N: khi 1 object đổi state, mọi observer được notify tự động.

Vấn đề Nhiều object cần phản ứng khi 1 object thay đổi — không muốn polling. Lợi ích Loose coupling. Subject không biết observer cụ thể. Đánh đổi Order notify khó kiểm soát. Memory leak nếu quên unsubscribe.

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

PatternQuan hệUse case mẫu
Chain of ResponsibilityChain handlerExpress middleware
CommandAction như objectUndo/redo, queue
Iterator1 collectionTree traversal, lazy stream
MediatorN-N qua trung gianUI form, chat room
MementoSnapshot stateSave/load, undo
Observer1-N publishEvent listener, reactive

9. Bài tập

  1. Chain of Responsibility: implement validation chain cho form đăng ký: NotEmpty → ValidEmail → UniqueEmail → StrongPassword. Mỗi handler trả error hoặc pass tiếp.
  2. Command: implement remote control TV với button Volume+, Volume-, Mute, Unmute. Hỗ trợ undo cho lần bấm gần nhất.
  3. Iterator (generator): viết hàm function* primes(): Generator<number> sinh số nguyên tố vô hạn. Dùng for...of với break để lấy 100 số đầu.
  4. 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.
  5. Memento: Game character có hp, mp, position. Implement save/load state qua Memento. Demo undo move sai.
  6. Observer: tạo simple Pub/Sub cho stock price. 3 subscriber: Logger, Alert (giá > 100), Chart (đổi UI). Demo unsubscribe.
  7. 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?

  • CSS specificity
  • React hooks
  • Express/Koa middleware — chuỗi xử lý request
  • CSS variables
Express middleware là Chain of Responsibility kinh điển: 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:

  • Tạo Singleton
  • Undo/redo, queue, log, hoặc schedule action
  • Inheritance đa cấp
  • Tách abstraction
Command đóng gói action thành object → có thể store, queue, log, undo. Use case: text editor undo, job queue, macro recording, transaction. Trong FP, function/closure tự nó là Command — không cần class.

JavaScript generator (function*):

  • Tạo class
  • Bắt buộc cho async
  • Cấu trúc cây
  • Cách đơn giản tạo iterator — yield giá trị, lazy evaluation, hỗ trợ infinite sequence
Generator là syntax đẹp cho Iterator pattern. 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ừ:

  • N×N (mọi object kết nối nhau) → N-1 (mỗi object chỉ biết mediator)
  • N×N → 1×1
  • N → 0
  • Không thay đổi coupling
Trước Mediator, K object giao tiếp trực tiếp = K(K-1)/2 connection. Sau, chỉ K connection (mỗi object → mediator). Đổi component không ảnh hưởng các component khác. Trade-off: mediator có thể phình thành god object.

Memento KHÁC Command-undo ở chỗ:

  • Memento nhanh hơn
  • Command nhanh hơn
  • Memento lưu full snapshot; Command lưu delta để inverse — tradeoff memory vs complexity
  • Hai cái như nhau
Memento: snapshot toàn bộ state trước action → đơn giản, đắt memory. Command undo: lưu inverse op → tiết kiệm, phức tạp. Memento cho object nhỏ (form), Command cho action có inverse rõ (text edit).

Observer pattern xuất hiện rõ ràng trong:

  • Composite
  • DOM event listener, Redux store.subscribe, React useState, RxJS Observable
  • Singleton
  • Bridge
Observer là 1 trong những pattern phổ biến nhất. Mọi event-driven system đều dùng. RxJS mở rộng pattern này thành Observable với operators (map, filter, debounce). Pitfall: nhớ unsubscribe trong React useEffect cleanup.

Pitfall lớn nhất của Observer:

  • Chậm
  • Tốn RAM
  • Khó implement
  • Memory leak nếu quên unsubscribe — subject giữ reference observer, observer không bị GC
Subject lưu danh sách observer. Observer không unsubscribe → giữ reference → không GC. Phổ biến: React component unmount không cleanup subscription. Luôn return unsubscribe function trong useEffect.

Observer vs Mediator:

  • Observer = 1-N pub-sub (subject không biết observer); Mediator = N-N decoupling qua trung gian biết các bên
  • Hai cái như nhau
  • Observer chỉ cho UI
  • Mediator chỉ cho networking
Observer: subject publish event, observer subscribe (1-N, anonymous). Mediator: nhiều object giao tiếp qua trung gian (N-N coordination, mediator biết các bên). Use case khác — đôi khi kết hợp.

Hoàn thành Chương 6 (18/23 patterns). Tiếp theo: Chương 7 — Behavioral Part 2 (5 mẫu) →