🧠 Tổng quan
Phần này tập trung vào các Behavioral Design Patterns giúp quản lý trạng thái và hành vi của đối tượng theo thời gian – bằng cách tách biệt logic trạng thái ra khỏi hành vi của đối tượng.
📌 Các mẫu thiết kế sẽ được đề cập:
- Iterator
- Memento
- State
- Template Method
- Visitor
🔁 Iterator Pattern – Mẫu thiết kế Lặp
🎯 Mục đích
Cung cấp một cách tiếp cận thống nhất để duyệt qua các phần tử của một collection mà không cần biết chi tiết cấu trúc bên trong của nó.
🔍 Khái niệm chính
📦 1. Trừu tượng hóa việc duyệt
- Logic duyệt tách biệt hoàn toàn với cấu trúc của collection.
🧩 2. Giao diện thống nhất
- Cho phép viết code chung để làm việc với các loại collection khác nhau (mảng, cây, đồ thị…).
🔐 3. Giấu chi tiết bên trong
- Collection có thể thay đổi cách triển khai mà không ảnh hưởng tới code client.
📌 Ví dụ: Bạn có danh sách phim yêu thích nằm ở nhiều thư mục khác nhau, nhưng trong ứng dụng UI, bạn chỉ cần bấm “next” để xem từng phim một, không cần biết chính xác file đó ở đâu trên ổ cứng.
📌 Khi nào nên dùng Iterator Pattern?
Tình huống sử dụng | Mô tả |
---|---|
🌲 Cấu trúc phức tạp | Ví dụ như cây, đồ thị, hoặc collection tùy chỉnh |
🔁 Nhiều cách duyệt | Như in-order, pre-order, post-order trong cây |
🔄 Tách biệt client & collection | Client không cần biết chi tiết collection |
🧵 Duyệt song song | Nhiều con trỏ duyệt cùng lúc |
💤 Duyệt lười (Lazy Evaluation) | Chỉ sinh phần tử khi cần |
🤝 Giao diện thống nhất | Code có thể hoạt động với mọi collection |
📐 Cấu trúc UML

Các thành phần chính:
- Iterator: Giao diện định nghĩa
hasNext()
vànext()
- Aggregate: Giao diện định nghĩa
createIterator()
- ConcreteIterator: Hiện thực việc duyệt, lưu trữ vị trí hiện tại
- ConcreteAggregate: Collection cụ thể, có thể tạo ra Iterator
- Client: Gọi
createIterator()
và dùngnext()
để duyệt
💡 Lợi ích: Client chỉ cần quan tâm đến interface, không phụ thuộc vào chi tiết nội bộ collection.
👨💻 Cài đặt cụ thể
🧱 Khởi tạo các class cơ bản:
class Song { constructor(public title: string, public artist: string, public duration: number) {} toString(): string { return `${this.title} by ${this.artist} (${this.duration} seconds)`; } } interface Iterator<T> { hasNext(): boolean; next(): T | null; } interface Playlist { createIterator(): Iterator<Song>; }
🔄 Triển khai Iterator:
class PlaylistIterator implements Iterator<Song> { private currentIndex = 0; constructor(private playlist: Song[]) {} hasNext(): boolean { return this.currentIndex < this.playlist.length; } next(): Song | null { if (this.hasNext()) { return this.playlist[this.currentIndex++] || null; } return null; } private loadMoreSongs(): void { // Có thể load thêm từ API } } class MusicPlaylist implements Playlist { private songs: Song[] = []; addSong(song: Song): void { this.songs.push(song); } createIterator(): Iterator<Song> { return new PlaylistIterator(this.songs); } }
▶️ Sử dụng:
const myPlaylist = new MusicPlaylist(); myPlaylist.addSong(new Song("Bohemian Rhapsody", "Queen", 354)); myPlaylist.addSong(new Song("Stairway to Heaven", "Led Zeppelin", 482)); myPlaylist.addSong(new Song("Imagine", "John Lennon", 183)); const iterator = myPlaylist.createIterator(); console.log("My Playlist:"); while (iterator.hasNext()) { const song = iterator.next(); if (song) { console.log(song.toString()); } }
⚙️ Phiên bản async – Iterator bất đồng bộ
class AsyncSongIterator { private currentIndex = 0; private songs: string[] = []; private async fetchSongs(): Promise<string[]> { await new Promise((resolve) => setTimeout(resolve, 1000)); return ["Song 1", "Song 2", "Song 3"]; } async next(): Promise<{ value: string | null; done: boolean }> { if (this.currentIndex === 0) { this.songs = await this.fetchSongs(); } if (this.currentIndex < this.songs.length) { return { value: this.songs[this.currentIndex++], done: false }; } else { return { value: null, done: true }; } } [Symbol.asyncIterator]() { return this; } }
✅ Sử dụng với for await
:
const songIterator = new AsyncSongIterator(); for await (const song of songIterator) { console.log(song); }
🧪 Kiểm thử
📋 Những gì cần test:
- Gọi
next()
vàhasNext()
trả về đúng phần tử tiếp theo createIterator()
phải trả về đúng thể hiệnConcreteIterator
📦 Chạy test:
npm run test --iterator
⚠️ Hạn chế
Điểm hạn chế | Giải thích |
---|---|
🧩 Không cần thiết cho collection đơn giản | Dùng mảng thông thường có thể đủ |
🛠️ Tăng số lượng class | Cần bảo trì nhiều file hơn |
📚 Quá mức cho collection chỉ đọc | Có thể dùng trực tiếp array hoặc list là đủ |
🌐 Ứng dụng thực tế – RxJS
const observable = new Observable<number>((observer) => { observer.next(1); observer.next(2); observer.next(3); observer.complete(); }); observable .map((x) => x * 10) .filter((x) => x > 10) .subscribe((value) => console.log(value));
✅ RxJS sử dụng Iterator để quản lý các dòng dữ liệu bất đồng bộ, sử dụng for...of
hoặc for await...of
nhờ vào các thuộc tính Symbol.iterator
và Symbol.asyncIterator
.
🧠 Memento Pattern – Mẫu thiết kế Ghi nhớ
🎯 Mục đích
Cho phép lưu lại snapshot (bản chụp trạng thái) của một đối tượng tại một thời điểm cụ thể mà không phá vỡ tính đóng gói (encapsulation).
🔍 Cấu trúc chính
Thành phần | Vai trò |
---|---|
🧠 Originator | Đối tượng có trạng thái cần lưu |
🧾 Memento | Chứa trạng thái được lưu lại (snapshot) |
💼 Caretaker | Quản lý các memento nhưng không thao tác trực tiếp nội dung của chúng |
📌 Ưu điểm:
Tách biệt việc lưu trữ và khôi phục trạng thái khỏi logic xử lý, vẫn đảm bảo tính đóng gói (encapsulation).
✅ Khi nào dùng Memento Pattern?
Tình huống | Mô tả |
---|---|
🕰️ Lưu/khôi phục trạng thái | Undo/Redo, checkpoint, quay về trạng thái trước đó |
🛡️ Bảo toàn tính đóng gói | Trạng thái không bị truy cập trực tiếp từ bên ngoài |
🧩 Trạng thái phức tạp | Khi logic nội bộ cần checkpoint thường xuyên (biên tập văn bản, game, mô phỏng…) |
📐 Sơ đồ UML

Các thành phần:
- AppState: Giao diện cho trạng thái
- Originator: Tạo Memento từ trạng thái hiện tại
- Memento: Lưu giữ trạng thái
- Caretaker: Quản lý danh sách các memento
Cách hoạt động:
Originator.save()
→ trả vềMemento
Caretaker
lưu lạiMemento
Caretaker.restore()
→ truyềnMemento
ngược lại choOriginator
👨💻 Cài đặt
interface TextEditorState { content: string; cursorPosition: number; } class EditorMemento { constructor(private readonly state: TextEditorState) {} getState(): TextEditorState { return this.state; } }
📝 Lưu trạng thái nội dung + vị trí con trỏ.
▶️ Sử dụng trong thực tế
const editor = new TextEditor(); const caretaker = new EditorCaretaker(editor); editor.type("Hello, "); caretaker.save(); editor.type("world!"); caretaker.save(); console.log(editor.getContent()); // Hello, world! caretaker.undo(); console.log(editor.getContent()); // Hello, caretaker.redo(); console.log(editor.getContent()); // Hello, world!
🧪 Kiểm thử
🔍 Các trường hợp cần test:
- Gọi
save()
lưu đúng trạng thái - Gọi
undo()
vàredo()
khôi phục đúng trạng thái - Không thay đổi nội dung
Memento
từCaretaker
📦 Lệnh chạy test:
npm run test --memento
⚠️ Hạn chế
Hạn chế | Giải thích |
---|---|
⚙️ Tăng phức tạp | Thêm nhiều lớp và quan hệ |
🧠 Ngốn bộ nhớ | Lưu nhiều snapshot có thể gây tốn RAM |
🐢 Ảnh hưởng hiệu năng | Snapshot/phục hồi liên tục với trạng thái phức tạp có thể làm chậm ứng dụng |
🌐 Ứng dụng thực tế – Visual Studio Code
Thành phần | Vai trò |
---|---|
ITextSnapshot | 🧾 Memento: snapshot bất biến của văn bản |
TextModel | 🧠 Originator: tạo snapshot |
EditStack | 💼 Caretaker: lưu các bước chỉnh sửa |
🧠 State Pattern – Mẫu thiết kế Trạng thái
🎯 Mục đích
Cho phép đối tượng thay đổi hành vi khi trạng thái nội bộ thay đổi, không cần dùng nhiều câu lệnh if-else hoặc switch-case.
🔍 Cấu trúc chính
Thành phần | Vai trò |
---|---|
🧠 Context | Giữ trạng thái hiện tại và ủy quyền xử lý |
🧾 State | Giao diện khai báo các hành vi |
🧩 ConcreteStateA/B/… | Thể hiện hành vi cụ thể theo từng trạng thái |
📌 Ý tưởng: Đối tượng Context
không tự xử lý hành vi theo trạng thái, mà giao cho State
xử lý.
✅ Khi nào dùng State Pattern?
Trường hợp | Lợi ích |
---|---|
🤹 Hành vi thay đổi theo trạng thái | Tránh lặp if/else lớn |
🔁 Thay đổi trạng thái linh hoạt | Dễ quản lý, kiểm soát logic chuyển trạng thái |
🧽 Giảm lặp mã | Tách biệt xử lý cho từng trạng thái |
🔧 Thay đổi lúc runtime | Có thể hoán đổi trạng thái trong khi chạy |
🧱 Mở rộng dễ dàng | Thêm trạng thái mới mà không sửa code Context |
📐 Sơ đồ UML

Context
sử dụngState
(composition)State
là interfaceConcreteStates
hiện thựcState
, định nghĩa hành vi cụ thểContext.changeState()
→ đổi trạng thái hiện tại
👨💻 Cài đặt mẫu
interface State { handle(): void; } class Context { private state: State; constructor(initialState: State) { this.state = initialState; } request(): void { this.state.handle(); } changeState(newState: State): void { if (this.state instanceof ConcreteStateA && !(newState instanceof ConcreteStateB)) { throw new Error("Invalid state transition"); } this.state = newState; } } class ConcreteStateA implements State { handle(): void { console.log("Handling request in ConcreteStateA"); } } class ConcreteStateB implements State { handle(): void { console.log("Handling request in ConcreteStateB"); } }
▶️ Dùng thử
const context = new Context(new ConcreteStateA()); context.request(); // => Handling request in ConcreteStateA context.changeState(new ConcreteStateB()); context.request(); // => Handling request in ConcreteStateB
🧪 Kiểm thử
📋 Các test case cần có:
- Kiểm tra đúng hành vi của từng
ConcreteState
- Đảm bảo
changeState()
chuyển đổi đúng - Kiểm tra lỗi khi chuyển trạng thái không hợp lệ
📦 Lệnh chạy test:
bashCopyEditnpm run test --state
⚠️ Hạn chế
Hạn chế | Diễn giải |
---|---|
⚙️ Tăng lớp | Có thể tạo ra nhiều class nếu trạng thái không khác nhau nhiều |
🧩 Có thể over-engineer | Trường hợp đơn giản thì dùng enum/switch-case có thể gọn hơn |
🪤 Khó định nghĩa rõ ràng trạng thái | Nếu sự khác biệt nhỏ thì dễ tạo ra nhiều mã boilerplate |
🌐 Ứng dụng thực tế – React Router
Thành phần | Vai trò |
---|---|
RouteState | State interface cho các loại khớp đường dẫn |
Route | Context, chọn state phù hợp theo path |
ConcreteStates | Dùng cho so khớp đường dẫn dạng string hoặc regex |
interface RouteState { match(path: string): boolean; render(component: React.ComponentType): React.ReactElement; } class Route { private state: RouteState; constructor(path: string | RegExp) { // Chọn state tương ứng } matches(path: string): boolean { return this.state.match(path); } }
🧱 Template Method Pattern – Mẫu Thiết kế Phương Thức Mẫu
🎯 Mục đích
Xác định một khuôn mẫu (template) cho thuật toán trong lớp cơ sở trừu tượng, cho phép lớp con tùy biến một vài bước mà không làm thay đổi cấu trúc tổng thể.
🔍 Thành phần chính
Thành phần | Vai trò |
---|---|
📄 Abstract base class | Định nghĩa thuật toán tổng thể qua phương thức template |
🧩 Template Method | Khung xương của thuật toán (gọi các bước) |
🧱 Concrete operations | Các bước được cài sẵn trong lớp cơ sở |
🧬 Abstract operations | Các bước để lớp con ghi đè |
🔘 Hook methods | Bước tùy chọn, có thể ghi đè hoặc không |
📌 Ý tưởng:
Định nghĩa một thuật toán chuẩn, và để lớp con lấp vào những chỗ còn trống.
✅ Khi nào dùng Template Method?
Tình huống | Lợi ích |
---|---|
📋 Có thuật toán có cấu trúc cố định nhưng vài bước thay đổi | Ví dụ: xử lý tài liệu PDF, Word, HTML khác nhau nhưng cùng luồng |
🧹 Loại bỏ trùng lặp mã | Các bước giống nhau được định nghĩa 1 lần |
🧰 Xây framework mở rộng | Cho phép người dùng framework tùy chỉnh hành vi từng bước |
📐 Sơ đồ UML

- DocumentProcessor: lớp cơ sở, chứa
processDocument()
là template - openDocument(), saveDocument(): bước cố định
- extractContent(), analyzeContent(): bước trừu tượng
- PDFProcessor, WordProcessor: lớp con triển khai các bước trừu tượng
👨💻 Cài đặt cụ thể
abstract class DocumentProcessor { public processDocument(): void { this.openDocument(); this.extractContent(); this.analyzeContent(); this.saveDocument(); } protected openDocument(): void { console.log("Opening document"); } protected abstract extractContent(): void; protected abstract analyzeContent(): void; protected saveDocument(): void { console.log("Saving processed document"); } } class PDFProcessor extends DocumentProcessor { protected extractContent(): void { console.log("Extracting content from PDF"); } protected analyzeContent(): void { console.log("Analyzing PDF content"); } }
▶️ Sử dụng
tsCopyEditconst pdfDoc: DocumentProcessor = new PDFProcessor();
pdfDoc.processDocument();
🧪 Kiểm thử
📋 Kiểm tra:
- Thứ tự gọi các bước
- Lớp con thực thi đúng bước riêng
- Không gọi nhầm hook hoặc abstract method
📦 Lệnh test:
npm run test --template-method
⚠️ Hạn chế
Hạn chế | Giải thích |
---|---|
🔧 Khó nâng cấp nếu đã phân phối | Việc loại bỏ/đổi thứ tự bước ảnh hưởng đến subclass |
🎯 Giới hạn mở rộng linh hoạt | Nên giữ số bước tối thiểu để tránh phá vỡ subclass |
🌐 Ứng dụng thực tế
🔷 React Class Component
class WelcomeHome extends React.Component<{ name: string }> { componentDidMount() { console.log("Just loaded"); } componentWillUnmount() { console.log("Goodbye!"); } render() { return <h1>Hello, {this.props.name}</h1>; } }
render()
là bắt buộc (template)- Các hook như
componentDidMount
là tùy chọn
🟦 Angular Life Cycle
export class UserProfileComponent implements OnInit { userData: string; ngOnInit(): void { this.initialize(); this.loadData(); } private initialize(): void { console.log('Initializing User Profile'); } private loadData(): void { this.userData = 'User data loaded from API'; } }
ngOnInit()
như một template method, gọi các bước nhỏ tùy chỉnh
👥 Visitor Pattern – Mẫu Thiết kế Khách Đến Nhà
🎯 Mục đích
Cho phép tách riêng các thao tác (logic) áp dụng cho một tập hợp các đối tượng không cần thay đổi lớp đối tượng đó.
🔍 Thành phần chính
Thành phần | Vai trò |
---|---|
🧾 Visitor | Giao diện định nghĩa các phương thức visit<Type> |
📦 ConcreteVisitor | Hiện thực các thao tác cụ thể cho từng loại đối tượng |
🧱 Element (AcceptsVisitor) | Giao diện cho phép chấp nhận visitor |
📄 ConcreteElement | Gọi đúng visitor.visit(this) theo loại |
✅ Khi nào dùng Visitor Pattern?
Trường hợp | Lợi ích |
---|---|
📊 Cần thu thập dữ liệu từ nhiều đối tượng dạng cây/phức tạp | Truy xuất sạch sẽ qua visitor |
🛠️ Cần thêm logic mới mà không sửa class gốc | Dễ bảo trì, mở rộng |
🔬 Nhiều thao tác khác nhau cho cùng object tree | Chỉ cần tạo Visitor mới thay vì thay object |
📐 Sơ đồ UML

DocumentVisitor
: Interface khai báo cácvisit<Type>()
PdfDocument
,WordDocument
: Gọiaccept(visitor)
CompositeDocument
: Chứa nhiều document con, gọi accept theo đệ quyDocumentProcessingVisitor
: Xử lý từng loại cụ thể
👨💻 Cài đặt ví dụ
export interface DocumentVisitor { visitPdfDocument(pdf: PdfDocument): void; visitWordDocument(word: WordDocument): void; visitCompositeDocument(doc: CompositeDocument): void; } export interface AcceptsVisitor { accept(visitor: DocumentVisitor): void; } export class PdfDocument implements AcceptsVisitor { accept(visitor: DocumentVisitor): void { visitor.visitPdfDocument(this); } }
export class CompositeDocument implements AcceptsVisitor { private documents: AcceptsVisitor[] = []; addDocument(doc: AcceptsVisitor) { this.documents.push(doc); } accept(visitor: DocumentVisitor) { for (let doc of this.documents) { doc.accept(visitor); } visitor.visitCompositeDocument(this); } }
▶️ Sử dụng
const composite = new CompositeDocument(); composite.addDocument(new PdfDocument()); composite.addDocument(new WordDocument()); const visitor = new DocumentProcessingVisitor(); composite.accept(visitor);
🧪 Kiểm thử
📋 Test cần đảm bảo:
- Đúng trình tự gọi
visit<Type>()
- Đúng loại visitor được gọi
- Tập hợp composite thực hiện gọi đầy đủ
📦 Lệnh test:
bashCopyEditnpm run test --visitor
⚠️ Hạn chế
Hạn chế | Giải thích |
---|---|
🔗 Ràng buộc chặt | Thêm 1 loại đối tượng mới → phải sửa toàn bộ visitor |
❌ Dễ gọi nhầm visit | Sai phương thức visit<Type> sẽ lỗi |
🔒 Giới hạn truy cập | Visitor chỉ thấy được public , không thấy được trạng thái nội tại |
🌐 Ứng dụng thực tế
📌 Static Code Analysis Tool
interface ASTVisitor { visitFunctionDeclaration(node: FunctionDeclaration): void; } class ComplexityAnalyzer implements ASTVisitor { private complexity = 0; visitFunctionDeclaration(node: FunctionDeclaration) { this.complexity += 1; } }
📂 Được dùng trong:
- ✅ TypeScript Compiler
- ✅ @typescript-eslint – kiểm tra mã TypeScript, tạo rule ESLint tùy chỉnh
Tham khảo mã nguồn Visitor
📚 Tổng kết – Quản lý Trạng thái & Hành Vi
Pattern | Mục tiêu chính | Tình huống sử dụng |
---|---|---|
🔁 Iterator | Duyệt qua collection | Cấu trúc phức tạp, lazy loading |
🧠 Memento | Lưu/khôi phục trạng thái | Undo/Redo, checkpoint |
🔄 State | Thay đổi hành vi theo trạng thái | FSM, logic UI phức tạp |
🧰 Template Method | Tái sử dụng khung thuật toán | Xử lý tài liệu, hook life cycle |
👥 Visitor | Thêm thao tác cho nhiều đối tượng mà không sửa lớp gốc | AST, phân tích mã, composite tree |
Để lại một bình luận
Bạn phải đăng nhập để gửi bình luận.