

Mẫu thiết kế (Design Pattern)
- 18-06-2025
- Toanngo92
- 0 Comments
Mục lục
Giới thiệu – Vì sao cần Mẫu Thiết Kế (Design Patterns)?
🧠 1.1. Từ thiết kế hệ thống đến kỹ thuật triển khai thông minh
Ở những bài học trước, bạn đã học:
- Phân tích yêu cầu
- Xây dựng sơ đồ lớp
- Mô hình hóa hành vi người dùng
- Thiết kế sơ đồ trình tự và hoạt động
Những thứ đó giúp bạn hiểu hệ thống cần làm gì và có cấu trúc ra sao. Nhưng khi bắt tay vào viết mã, bạn sẽ thường xuyên gặp tình huống như:
“Làm sao khởi tạo object đúng cách mà không dùng if/else khắp nơi?”
“Làm sao để các phần của chương trình ít phụ thuộc vào nhau nhưng vẫn làm việc cùng nhau?”
Đây chính là lúc bạn cần đến Design Patterns – Mẫu thiết kế.
📌 1.2. Design Pattern là gì?
💬 Mẫu thiết kế là một giải pháp được kiểm chứng, có thể tái sử dụng, nhằm giải quyết một vấn đề thiết kế lặp lại trong phần mềm hướng đối tượng.
Bạn có thể hiểu mẫu thiết kế là:
- Một công thức thiết kế đã được cộng đồng phần mềm sử dụng và cải tiến
- Một giao thức giao tiếp giữa các lớp và đối tượng
- Một cách để giải quyết vấn đề quen thuộc bằng tư duy trừu tượng
🔧 1.3. Mẫu thiết kế không phải là đoạn mã copy-paste
Điều quan trọng là:
- Mẫu thiết kế không giống như thư viện có sẵn
- Nó không thay thế việc phân tích, hiểu hệ thống
- Mà là một chiến lược tổ chức mã để hệ thống dễ hiểu hơn, dễ mở rộng hơn, và ổn định hơn
🎯 Design Pattern giống như kiến trúc nhà – bạn vẫn phải tự xây, nhưng có mô hình sẵn để không “xây sai”.
🛠 1.4. Lý do cần học mẫu thiết kế
Lý do | Ý nghĩa thực tế |
---|---|
Giải quyết vấn đề quen thuộc | Không phải phát minh lại bánh xe |
Tăng tính mở rộng, bảo trì | Dễ thay đổi từng phần mà không vỡ toàn bộ |
Tạo ra “từ vựng thiết kế” | Dễ trao đổi trong nhóm: “Đây là pattern Factory” |
Tăng kỹ năng kiến trúc phần mềm | Đưa ra quyết định thiết kế cấp cao, chuẩn mực |
🧩 1.5. Khi nào bạn biết mình cần đến một Design Pattern?
Nếu bạn thấy mình:
- Viết nhiều
if...else
để tạo object - Phải thay đổi code ở nhiều file khác nhau khi thêm chức năng
- Không kiểm soát được sự phụ thuộc giữa các class
- Thường xuyên viết lại cùng một kiểu logic tạo đối tượng / quản lý trạng thái
→ Có thể bạn đang thiếu một mẫu thiết kế phù hợp.
📚 1.6. Mẫu thiết kế phổ biến đến từ đâu?
Hầu hết các mẫu thiết kế hiện đại được tổng hợp từ cuốn sách:
“Design Patterns: Elements of Reusable Object-Oriented Software”
(của nhóm “Gang of Four” – GOF: Gamma, Helm, Johnson, Vlissides)
💬 Ghi nhớ chương này:
- Design Pattern là cách suy nghĩ, không phải đoạn mã
- Được sinh ra từ kinh nghiệm thực tế, được chuẩn hóa thành kiến thức tái sử dụng
- Là bước chuyển từ thiết kế kỹ thuật sang tư duy kiến trúc phần mềm
🧠 Học pattern không giúp bạn code nhanh hơn ngay lập tức – nhưng giúp bạn code đúng ngay từ đầu.
Creational design pattern
Mẫu thiết kế Factory (Factory Pattern)
🎯 2.1. Mục tiêu của Factory Pattern là gì?
Factory Pattern là mẫu thiết kế hướng đến việc tách biệt việc khởi tạo đối tượng khỏi phần sử dụng đối tượng đó.
Bạn không muốn mỗi lần tạo object lại phải viết:
javaCopyEditif (type.equals("Circle")) return new Circle();
else if (type.equals("Rectangle")) return new Rectangle();
Thay vào đó, bạn muốn:
javaCopyEditShape s = ShapeFactory.getShape("Circle");
→ Tối ưu hơn, dễ bảo trì hơn, dễ mở rộng hơn.
🔍 2.2. Khi nào nên dùng Factory Pattern?
Dấu hiệu bạn cần Factory | Vì sao? |
---|---|
Bạn dùng nhiều new ở nhiều nơi | Dễ gây phụ thuộc, khó sửa |
Có nhiều loại object cùng kế thừa | Ví dụ: Shape , Animal , PaymentMethod |
Muốn ẩn cách object được tạo ra | Giúp bảo mật, kiểm soát |
Muốn tái sử dụng logic khởi tạo | Tránh lặp code |
🧠 2.3. Cấu trúc tổng quát của Factory Pattern
Thành phần | Mô tả |
---|---|
Interface (Product) | Giao diện chung cho các object được tạo |
Concrete Class | Các lớp cụ thể thực thi interface |
Factory Class | Chứa phương thức để khởi tạo object theo yêu cầu |
🧪 2.4. Ví dụ: Vẽ hình
Giả sử bạn có giao diện:
javaCopyEditpublic interface Shape {
void draw();
}
Và các lớp triển khai:
javaCopyEditpublic class Circle implements Shape {
public void draw() {
System.out.println("Vẽ hình tròn");
}
}
public class Rectangle implements Shape {
public void draw() {
System.out.println("Vẽ hình chữ nhật");
}
}
Factory:
javaCopyEditpublic class ShapeFactory {
public Shape getShape(String shapeType) {
if (shapeType == null) return null;
if (shapeType.equalsIgnoreCase("CIRCLE")) return new Circle();
if (shapeType.equalsIgnoreCase("RECTANGLE")) return new Rectangle();
return null;
}
}
Sử dụng:
javaCopyEditShapeFactory factory = new ShapeFactory();
Shape s = factory.getShape("CIRCLE");
s.draw(); // Output: Vẽ hình tròn
✅ 2.5. Lợi ích chính của Factory Pattern
Lợi ích | Giải thích |
---|---|
Tách rời logic khởi tạo | Giảm phụ thuộc giữa module |
Dễ mở rộng | Thêm class mới không cần sửa code gọi |
Giảm lặp code | Logic khởi tạo tập trung tại một nơi |
Áp dụng đa hình | Sử dụng chung một kiểu interface |
⚠️ 2.6. Cảnh báo khi dùng Factory
- Đừng tạo Factory quá đơn giản, rồi vẫn quản lý kiểu (type) bằng if/else ở nơi khác
- Nếu chỉ có 1–2 loại class thì Factory có thể làm mọi thứ trở nên rườm rà không cần thiết
- Factory không thay thế cho hiểu biết nghiệp vụ – bạn vẫn phải biết mình đang tạo cái gì
📌 2.7. So sánh Factory và khởi tạo trực tiếp
Tiêu chí | new trực tiếp | Factory |
---|---|---|
Dễ dùng với project nhỏ | ✅ | ❌ |
Mở rộng dễ khi thêm class mới | ❌ | ✅ |
Dễ kiểm soát việc tạo object | ❌ | ✅ |
Tái sử dụng logic tạo | ❌ | ✅ |
Có thể thay thế class bằng config | ❌ | ✅ |
💬 Ghi nhớ chương này:
- Factory Pattern giúp bạn tổ chức mã khởi tạo đối tượng một cách hợp lý
- Nó rất hữu ích khi:
- Có nhiều object cùng giao diện
- Muốn ẩn đi chi tiết tạo object
- Đừng lạm dụng – chỉ nên dùng khi thấy mình viết nhiều
new
hoặcif/else
🔑 Một hệ thống tốt là hệ thống mà nơi “biết cách tạo ra đối tượng” được kiểm soát chặt – Factory Pattern giúp bạn làm điều đó.
Abstract Factory
Khi bạn cần nhiều factory cho nhiều loại đối tượng
3.1. Vấn đề: Khi Factory chưa đủ
Factory Pattern giúp bạn:
- Tạo object dựa trên type
- Dễ thay đổi, dễ mở rộng
Nhưng điều gì xảy ra nếu:
Bạn không chỉ tạo một đối tượng, mà là nhiều đối tượng liên quan với nhau?
Ví dụ:
- Giao diện trên Windows và Mac có:
Button
Textbox
Checkbox
→ Bạn không muốn dùng if
để chọn cho từng loại → mà cần chọn cả bộ giao diện một cách nhất quán.
→ Đây là lúc bạn cần Abstract Factory Pattern.
🧭 3.2. Mục tiêu của Abstract Factory
Cung cấp một giao diện để tạo ra một nhóm đối tượng liên quan mà không chỉ rõ lớp cụ thể.
Bạn có thể:
- Tạo
WindowsFactory
tạo raWindowsButton
,WindowsTextbox
- Tạo
MacFactory
tạo raMacButton
,MacTextbox
→ Gọi đến cùng interface UIFactory
, nhưng ra “bộ sản phẩm” khác nhau.
🧱 3.3. Cấu trúc tổng quát
Thành phần | Mô tả |
---|---|
AbstractFactory | Interface/khuôn mẫu của tất cả factory |
ConcreteFactory | Factory thực sự – ví dụ: WinFactory , MacFactory |
AbstractProduct | Interface chung cho sản phẩm (Button, Textbox) |
ConcreteProduct | Sản phẩm cụ thể (WinButton, MacButton) |
🧪 3.4. Ví dụ: Giao diện người dùng nhiều nền tảng
Giao diện chung:
javaCopyEditinterface Button {
void paint();
}
interface GUIFactory {
Button createButton();
}
Hai lớp cụ thể:
javaCopyEditclass WinButton implements Button {
public void paint() {
System.out.println("Windows Button");
}
}
class MacButton implements Button {
public void paint() {
System.out.println("Mac Button");
}
}
Các Factory:
javaCopyEditclass WinFactory implements GUIFactory {
public Button createButton() {
return new WinButton();
}
}
class MacFactory implements GUIFactory {
public Button createButton() {
return new MacButton();
}
}
Sử dụng:
javaCopyEditGUIFactory factory = OS.isMac() ? new MacFactory() : new WinFactory();
Button button = factory.createButton();
button.paint(); // Hiển thị theo hệ điều hành
✅ 3.5. Lợi ích của Abstract Factory
Lợi ích | Tác dụng |
---|---|
Tạo nhiều sản phẩm đồng bộ | Không lo sai loại khi trộn nền tảng |
Thay đổi toàn bộ “bộ giao diện” dễ dàng | Thay factory là đổi toàn bộ sản phẩm |
Tăng khả năng mở rộng | Dễ thêm LinuxFactory , DarkThemeFactory |
Tách biệt logic khởi tạo | Không cần if...else khắp nơi |
⚠️ 3.6. Khi nào KHÔNG nên dùng Abstract Factory?
Dấu hiệu | Vì sao không nên dùng |
---|---|
Chỉ có 1–2 loại sản phẩm | Mẫu này làm mọi thứ rối hơn |
Hệ thống đơn giản, không cần đồng bộ | Dùng Factory đơn giản là đủ |
Không có logic tạo động phức tạp | Không cần thêm abstract layer |
🔄 3.7. Factory vs Abstract Factory
So sánh | Factory | Abstract Factory |
---|---|---|
Tạo ra | Một đối tượng | Một nhóm đối tượng liên quan |
Mức độ trừu tượng | Trung bình | Cao |
Dễ triển khai | Dễ hơn | Phức tạp hơn, cần nhiều class |
Dễ mở rộng hệ thống lớn | Có giới hạn | Rất tốt khi cần mở rộng theo “bộ sản phẩm” |
Thay đổi theo hoàn cảnh | Khó | Dễ – chỉ cần đổi factory |
📦 3.8. Ứng dụng thực tế của Abstract Factory
- Giao diện nhiều nền tảng: Windows, macOS, Android, Web
- Chủ đề (theme): Dark, Light, High Contrast
- Game: bộ quái vật theo level/map
- Web CMS: thay đổi toàn bộ layout bằng switching builder/theme
💬 Ghi nhớ chương này:
- Abstract Factory là nâng cấp của Factory khi bạn cần tạo nhiều object “cùng nhóm”
- Rất mạnh cho hệ thống hỗ trợ nhiều phiên bản, giao diện, ngữ cảnh
- Dễ áp dụng với các hệ thống lớn, đòi hỏi cấu hình linh hoạt
🔑 Nếu bạn cần chọn “gói sản phẩm” thay vì “một sản phẩm” → hãy chọn Abstract Factory.
Khi nào dùng các mẫu tạo đối tượng (Creational Patterns)
🧠 4.1. Mục tiêu của nhóm Creational Patterns
Nhóm Creational Patterns tập trung vào một vấn đề chung:
Làm thế nào để tạo ra object một cách linh hoạt, rõ ràng, dễ mở rộng, và đúng nguyên lý thiết kế?
Vì vậy, các mẫu trong nhóm này giúp:
- Tách rời logic khởi tạo khỏi logic sử dụng
- Dễ dàng chuyển đổi cấu hình hoặc mở rộng hệ thống
- Ẩn chi tiết khởi tạo phức tạp
🧭 4.2. Tổng quan các mẫu Creational phổ biến
Mẫu | Mục tiêu chính |
---|---|
Factory Method | Tạo object theo lựa chọn đầu vào |
Abstract Factory | Tạo một “bộ sản phẩm” đồng bộ |
Builder | Tạo object phức tạp theo từng bước |
Prototype | Nhân bản object từ bản mẫu có sẵn |
Singleton | Đảm bảo chỉ có 1 instance tồn tại toàn hệ thống |
📊 4.3. So sánh nhanh các mẫu Factory
Tiêu chí | Factory Method | Abstract Factory |
---|---|---|
Tạo ra | 1 object đơn lẻ | Bộ object liên quan |
Mức độ trừu tượng | Trung bình | Cao hơn |
Phù hợp khi | Có nhiều loại sản phẩm khác nhau | Có nhiều nhóm sản phẩm (UI theo theme…) |
Dễ triển khai | ✅ | ❌ (nhiều lớp hơn) |
Dễ thay đổi toàn bộ sản phẩm | ❌ (chỉ từng cái) | ✅ (chỉ đổi factory) |
Ví dụ | ShapeFactory → Circle | GUIFactory → WinButton + WinTextbox |
🧪 4.4. Khi nào dùng Factory vs Abstract Factory?
Tình huống | Mẫu phù hợp |
---|---|
Chọn một đối tượng từ danh sách (new A , new B , new C ) | ✅ Factory |
Cần tạo một bộ object thống nhất (cùng kiểu, cùng theme) | ✅ Abstract Factory |
Muốn chọn cấu hình linh hoạt toàn hệ thống (UI, bản đồ,…) | ✅ Abstract Factory |
Cần gói gọn logic tạo đối tượng, tránh if...else | ✅ Factory |
Chỉ có vài class, không cần mở rộng nhiều | ❌ Đừng dùng cả hai |
📦 4.5. Vị trí trong hệ thống phần mềm
Thành phần hệ thống | Mẫu thiết kế phù hợp |
---|---|
Giao diện theo nền tảng | Abstract Factory |
Plugin game (quái vật theo map) | Abstract Factory / Factory |
Quản lý phương thức thanh toán | Factory / Strategy |
Tạo đơn hàng, hóa đơn, hợp đồng | Builder (nếu phức tạp) |
Cấu hình kết nối CSDL | Singleton |
💬 4.6. Ghi nhớ chương này:
- Factory: dùng khi bạn có nhiều object cần khởi tạo linh hoạt
- Abstract Factory: dùng khi bạn cần đổi cả nhóm object đồng bộ
- Cả hai mẫu này đều giúp ẩn đi chi tiết khởi tạo, áp dụng Open/Closed Principle
🔑 Mẫu thiết kế tốt không nằm ở chỗ “áp dụng bao nhiêu”, mà ở việc áp dụng đúng lúc, đúng nơi.
Singleton Pattern
Một đối tượng duy nhất, kiểm soát toàn cục
🧠 5.1. Vấn đề đặt ra
Giả sử trong hệ thống bạn chỉ cần một đối tượng duy nhất tồn tại để:
- Ghi log
- Kết nối cơ sở dữ liệu
- Truy cập cấu hình hệ thống
- Quản lý tài nguyên (memory pool, thread pool…)
Bạn không muốn có 2 logger ghi chồng lên nhau
Bạn không muốn có 2 đối tượng kết nối trùng nhau đến cùng một database
🎯 5.2. Mục tiêu của Singleton
Đảm bảo rằng chỉ có một thể hiện duy nhất (instance) của một class tồn tại trong hệ thống, và cung cấp một điểm truy cập toàn cục đến nó.
🧱 5.3. Cấu trúc cơ bản của Singleton (Java/C#)
javaCopyEditpublic class Singleton {
private static Singleton instance;
private Singleton() {} // Constructor private
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
→ Bất cứ nơi nào trong hệ thống bạn gọi Singleton.getInstance()
sẽ luôn nhận về cùng một object duy nhất.
⚙️ 5.4. Singleton có gì đặc biệt?
Kỹ thuật áp dụng | Mục đích |
---|---|
Constructor private | Không cho phép new từ bên ngoài |
Biến static | Lưu thể hiện duy nhất |
Phương thức static | Cho phép gọi từ bất cứ đâu (global access) |
🔐 5.5. Các biến thể nâng cao
Biến thể Singleton | Khi nào dùng? |
---|---|
Thread-safe Singleton | Khi hệ thống có đa luồng (multi-threading) |
Lazy Initialization | Khi muốn tạo instance chỉ khi cần |
Eager Initialization | Tạo instance từ đầu chương trình |
Enum Singleton (Java) | Cách an toàn nhất trong Java |
🧪 5.6. Ví dụ ứng dụng thực tế của Singleton
Tình huống | Tại sao dùng Singleton? |
---|---|
Logger | Ghi log từ nhiều nơi nhưng đồng nhất |
ConfigManager | Cấu hình chỉ tải một lần |
DB Connection Pool | Quản lý kết nối tập trung |
Cache | Lưu dữ liệu dùng chung toàn hệ thống |
⚠️ 5.7. Những nhược điểm và lưu ý
Rủi ro | Hệ quả |
---|---|
Lạm dụng Singleton như biến toàn cục | Làm hệ thống khó test, rối phụ thuộc |
Không thread-safe | Có thể tạo nhiều instance ngoài ý muốn |
Khó mock trong unit test | Gây khó khăn cho kiểm thử tự động |
📌 Singleton tốt cho quản lý tài nguyên, nhưng không nên dùng để truyền dữ liệu hoặc làm nơi lưu trạng thái tạm thời.
📌 5.8. So sánh Singleton và Static class
Tiêu chí | Singleton | Static Class |
---|---|---|
Có thể kế thừa? | ✅ Có | ❌ Không |
Có thể triển khai interface? | ✅ | ❌ |
Có thể tạo mock/test dễ hơn? | ✅ | ❌ |
Luôn có sẵn? | ❌ (lười khởi tạo nếu cần) | ✅ (luôn tồn tại) |
Linh hoạt hơn? | ✅ | ❌ |
💬 Ghi nhớ chương này:
- Singleton = một class, một đối tượng duy nhất
- Hữu ích khi bạn muốn kiểm soát truy cập tài nguyên hoặc trạng thái toàn cục
- Hạn chế:
- Có thể gây phụ thuộc ngầm
- Không dễ test nếu không cẩn thận
- Dùng Singleton khi:
- Dữ liệu hoặc logic chỉ nên tồn tại ở một nơi duy nhất
- Bạn muốn kiểm soát việc tạo và sử dụng đối tượng đó
🔑 Singleton là pattern đơn giản nhất – nhưng nếu dùng không đúng, nó sẽ gây ra “gián điệp toàn cục” trong hệ thống.
Builder Pattern
Khi bạn cần xây dựng đối tượng phức tạp theo từng bước
🎯 6.1. Vấn đề: Khi constructor quá phức tạp
Giả sử bạn cần khởi tạo một object User
như sau:
javaCopyEditUser u = new User("John", "Doe", 30, "Engineer", null, true, false, "Admin");
❌ Rối mắt
❌ Khó đọc
❌ Không biết đối số thứ 6, 7, 8 là gì
❌ Gặp bug nếu truyền sai thứ tự
🧠 6.2. Builder Pattern giải quyết điều gì?
Cho phép bạn xây dựng một đối tượng phức tạp theo từng bước rõ ràng, có thể thay đổi thứ tự gọi, và dễ đọc.
Giống như cách bạn:
javaCopyEditPizza p = new PizzaBuilder()
.addCheese()
.addPepperoni()
.setSize("Large")
.build();
🧾 6.3. Cấu trúc Builder tổng quát
Thành phần | Vai trò |
---|---|
Builder | Lớp trung gian – chứa các phương thức để set giá trị |
ConcreteBuilder | Thực thi các bước cụ thể (có thể giống hoặc chính Builder) |
Product | Đối tượng được tạo ra |
Director (tuỳ chọn) | Điều phối việc gọi các bước builder |
✍️ 6.4. Ví dụ Java đơn giản
javaCopyEditpublic class User {
private String name;
private int age;
private String role;
// private constructor
private User(UserBuilder builder) {
this.name = builder.name;
this.age = builder.age;
this.role = builder.role;
}
public static class UserBuilder {
private String name;
private int age;
private String role;
public UserBuilder setName(String name) {
this.name = name;
return this;
}
public UserBuilder setAge(int age) {
this.age = age;
return this;
}
public UserBuilder setRole(String role) {
this.role = role;
return this;
}
public User build() {
return new User(this);
}
}
}
Sử dụng:
javaCopyEditUser user = new User.UserBuilder()
.setName("Alice")
.setAge(28)
.setRole("Admin")
.build();
✅ 6.5. Lợi ích của Builder Pattern
Lợi ích | Giải thích |
---|---|
Rõ ràng và dễ đọc | Nhìn thấy rõ từng bước thiết lập dữ liệu |
Tránh constructor quá tải | Không cần viết hàng loạt constructor với nhiều biến |
Dễ kiểm soát logic | Có thể thêm validate vào từng bước |
Có thể tạo các phiên bản mặc định | Giao diện builder dễ mở rộng |
Hữu ích với object bất biến | Vì mọi field được set qua builder, không cần setter public |
⚠️ 6.6. Cẩn trọng khi dùng Builder
Tình huống không phù hợp | Lý do |
---|---|
Object chỉ có 1–2 field | Dùng builder là dư thừa |
Không cần tính bất biến hoặc cấu hình nhiều bước | Constructor đơn giản là đủ |
Quá nhiều logic kiểm tra ràng buộc | Nên tách riêng ra class Validator |
🔁 6.7. Builder vs Factory vs Abstract Factory
Tiêu chí | Builder | Factory | Abstract Factory |
---|---|---|---|
Tạo từng bước? | ✅ Có | ❌ Không | ❌ Không |
Gắn với object phức tạp? | ✅ Phù hợp | ❌ Thường dùng cho object đơn giản | ✅ Dùng cho nhóm object liên quan |
Có thể tái sử dụng config? | ✅ | ❌ | ✅ |
Dễ đọc với chuỗi method | ✅ (method chaining) | ❌ | ❌ |
📦 6.8. Ứng dụng thực tế của Builder
- Form đăng ký/phân quyền người dùng phức tạp
- Tạo tài liệu, hóa đơn, báo cáo
- Tạo query SQL / API request
- Cấu hình UI thành phần nhiều bước
- Trong game: xây map, nhân vật từ nhiều module nhỏ
💬 Ghi nhớ chương này:
- Builder giúp bạn tạo object phức tạp từng bước một cách an toàn và rõ ràng
- Tránh constructor quá dài hoặc sai thứ tự tham số
- Áp dụng tốt khi:
- Object có nhiều field tuỳ chọn
- Cần kiểm soát quá trình khởi tạo
- Cần API mạch lạc, dễ đọc, dễ test
🧱 Builder không chỉ tạo object – nó tạo sự rõ ràng trong tư duy lập trình hướng đối tượng.
Prototype Pattern
Tạo bản sao mà không cần “new” lại từ đầu
🎯 7.1. Vấn đề cần giải quyết
Bạn có một object phức tạp với nhiều cấu hình, ví dụ:
javaCopyEditDocument doc = new Document();
doc.setHeader(...);
doc.setFooter(...);
doc.setStyle(...);
doc.loadTemplates();
Và bạn muốn tạo nhiều phiên bản giống y chang, nhưng chỉ thay đổi một vài phần nhỏ?
→ Bạn không muốn khởi tạo lại từ đầu (vì mất thời gian, tài nguyên)
→ Bạn cũng không muốn viết lại cùng một đoạn cấu hình dài
✅ 7.2. Giải pháp: Prototype Pattern
Prototype Pattern cho phép bạn tạo bản sao (clone) của một object hiện có, và sau đó tùy chỉnh nếu cần.
Bạn không new
đối tượng mới từ đầu, mà sao chép từ một bản mẫu có sẵn (prototype).
📦 7.3. Tình huống phù hợp để dùng Prototype
Tình huống | Lý do nên dùng |
---|---|
Object có quá trình khởi tạo tốn kém | Clone sẽ nhanh hơn new và setup |
Cần tạo nhiều bản sao chỉ khác chút ít | Tăng hiệu suất, giảm dư thừa |
Cần lưu trữ và phục hồi trạng thái | Dễ dàng sao chép & rollback |
🧱 7.4. Cấu trúc Prototype Pattern
Thành phần | Vai trò |
---|---|
Prototype Interface | Khai báo phương thức clone() |
Concrete Prototype | Triển khai clone(), chứa logic sao chép |
Client | Gọi clone() để tạo bản sao thay vì new |
✍️ 7.5. Ví dụ đơn giản trong Java
javaCopyEditpublic abstract class Shape implements Cloneable {
public int x, y;
public String color;
public Shape clone() throws CloneNotSupportedException {
return (Shape) super.clone();
}
}
public class Circle extends Shape {
public int radius;
}
Sử dụng:
javaCopyEditCircle circle1 = new Circle();
circle1.x = 10;
circle1.y = 20;
circle1.radius = 30;
Circle circle2 = (Circle) circle1.clone();
circle2.x = 50; // chỉnh nhẹ khác đi
⚠️ 7.6. Shallow vs Deep Copy
Kiểu sao chép | Ý nghĩa |
---|---|
Shallow Copy | Chỉ sao chép tham chiếu – dùng chung object con |
Deep Copy | Tạo ra object mới hoàn toàn, không phụ thuộc bản gốc |
Với object chứa các object khác → nên dùng deep copy để tránh lỗi ngầm
💡 7.7. Ưu điểm của Prototype Pattern
Ưu điểm | Giải thích |
---|---|
Giảm chi phí khởi tạo | Clone nhanh hơn setup lại toàn bộ |
Linh hoạt | Có thể sao chép + chỉnh sửa khác biệt |
Dễ mở rộng | Không cần tạo Factory cho từng loại |
Phù hợp với các hệ thống đồ họa | Vẽ, thiết kế, bản mẫu, UI layout… |
❌ 7.8. Nhược điểm / Cảnh báo
Vấn đề | Hệ quả |
---|---|
Khó kiểm soát nếu dùng shallow copy | Có thể thay đổi lẫn nhau không mong muốn |
Không rõ luồng nếu clone quá nhiều | Gây rối trạng thái object |
clone() trong Java không được khuyến khích | Vì dễ sai nếu object có nhiều tầng |
📌 Trong nhiều ngôn ngữ hiện đại, clone được thay thế bằng các thư viện serialization/deep copy chuyên biệt.
🧾 7.9. So sánh Prototype với các mẫu khác
Mẫu | Đặc điểm chính |
---|---|
Factory | Khởi tạo object mới từ input |
AbstractFactory | Tạo nhóm object liên quan |
Builder | Tạo object phức tạp theo bước |
Singleton | Đảm bảo chỉ có 1 instance tồn tại |
Prototype | Tạo object mới bằng cách clone từ mẫu đã có |
💬 Ghi nhớ chương này:
- Prototype là mẫu cho phép clone đối tượng để tái sử dụng dễ dàng
- Phù hợp với các hệ thống có:
- Quá trình setup object phức tạp
- Nhiều phiên bản gần giống nhau
- Tài nguyên hạn chế (không muốn khởi tạo lại)
🔁 Prototype = “copy thông minh”, chứ không phải “copy & paste”.
Hệ thống hóa nhóm Mẫu Thiết Kế Khởi Tạo (Creational Patterns)
🎯 8.1. Mục tiêu của các mẫu khởi tạo
Nhóm Creational Patterns tập trung vào “cách tạo đối tượng” sao cho:
- Dễ quản lý
- Linh hoạt theo cấu hình
- Tối ưu hiệu suất
- Dễ mở rộng, tái sử dụng
Khác với new ClassName()
đơn giản, nhóm này áp dụng tư duy kiến trúc để tạo object phù hợp với hoàn cảnh, quy mô hệ thống.
📚 8.2. Bảng tổng hợp 5 mẫu khởi tạo
Mẫu | Mục tiêu chính | Dùng khi nào? | Ví dụ thực tế |
---|---|---|---|
Factory Method | Tạo object từ input, giấu đi new | Nhiều class cùng interface, cần phân loại theo type | Hình vẽ: Circle, Rectangle, Star |
Abstract Factory | Tạo nhóm object cùng họ, đồng bộ | Hệ thống cần thay đổi cả “bộ sản phẩm” | Giao diện: Win/Mac/Dark Theme |
Singleton | Chỉ tạo một object toàn cục duy nhất | Quản lý cấu hình, logger, database… | Logger, CSDL, Config |
Builder | Tạo object phức tạp theo từng bước | Object nhiều thuộc tính tùy chọn, cần clarity | Pizza, User Form, SQL Builder |
Prototype | Tạo object bằng cách clone mẫu đã có | Cần nhân bản nhanh object cấu hình sẵn | UI Layout, File Template, GameUnit |
🧱 8.3. So sánh theo khía cạnh kỹ thuật
Tiêu chí | Factory | Abstract Factory | Singleton | Builder | Prototype |
---|---|---|---|---|---|
Giảm phụ thuộc (decoupling) | ✅ | ✅ | ❌ | ✅ | ✅ |
Linh hoạt mở rộng | ✅ | ✅✅ | ❌ | ✅ | ✅ |
Tạo object theo cấu hình | ✅ | ✅ | ❌ | ✅ | ✅ |
Khó hiểu đối với người mới | Trung bình | Cao | Thấp | Trung bình | Trung bình |
Hỗ trợ tạo object phức tạp | ❌ | ❌ | ❌ | ✅✅ | ✅ |
Cần nhiều class | Vừa | Cao | Thấp | Trung bình | Thấp–trung |
📌 8.4. Câu hỏi định hướng lựa chọn
Bạn nên chọn mẫu nào khi gặp những tình huống sau?
Câu hỏi | Mẫu phù hợp |
---|---|
Có nhiều loại object nhưng chung 1 interface? | Factory |
Cần tạo ra “bộ sản phẩm” nhất quán? | Abstract Factory |
Muốn đảm bảo chỉ có một bản ghi log hệ thống? | Singleton |
Tạo ra một hóa đơn, user form có nhiều trường tùy chọn? | Builder |
Cần nhân bản object mẫu? | Prototype |
🧠 8.5. Kết luận định hướng sử dụng
Nếu bạn cần… | Hãy chọn… |
---|---|
Ẩn việc khởi tạo, nhưng vẫn chọn được kiểu object | Factory |
Ẩn cả nhóm object và logic tạo ra chúng | Abstract Factory |
Có một object duy nhất toàn hệ thống | Singleton |
Dễ dàng tạo từng phần object (bước 1, bước 2…) | Builder |
Tạo nhanh bản sao của object đã tồn tại | Prototype |
💬 Ghi nhớ chương này:
- Creational Patterns = chiến lược để khởi tạo object đúng cách
- Chúng giúp hệ thống:
✅ Giảm phụ thuộc
✅ Dễ mở rộng
✅ Tối ưu hiệu suất
✅ Tăng khả năng test
🎯 Không có mẫu nào tốt nhất – chỉ có mẫu phù hợp nhất cho từng hoàn cảnh.
Nhóm Structural Patterns
🧠 9.1. Structural Patterns là gì?
Nếu nhóm Creational Patterns giúp bạn “tạo ra đối tượng đúng cách”, thì nhóm Structural Patterns sẽ giúp bạn:
Sắp xếp, liên kết và kết hợp các đối tượng/lớp một cách linh hoạt để tạo ra một hệ thống dễ hiểu, dễ mở rộng và có tổ chức.
🎯 9.2. Mục tiêu của nhóm Structural Patterns
- Tăng cường khả năng kết hợp giữa các đối tượng và class
- Tái sử dụng thành phần mà không phá vỡ cấu trúc
- Cho phép bạn thêm chức năng mới mà không cần sửa đổi class gốc
- Áp dụng nguyên lý mở–đóng (Open/Closed Principle) một cách rõ ràng
🧭 9.3. Khi nào bạn cần Structural Patterns?
Tình huống | Câu hỏi cần đặt ra |
---|---|
Cần tích hợp một class cũ vào hệ thống mới | Làm sao để “bọc” class đó mà không sửa code? |
Muốn kết hợp nhiều object nhỏ thành 1 tổng thể | Có mẫu nào giúp tạo “bộ phận tổng hợp”? |
Cần trang trí đối tượng bằng tính năng mới | Làm sao để thêm tính năng mà không sửa gốc? |
Giao diện cần chuyển đổi từ 1 API sang API khác | Làm sao chuyển đổi định dạng tương thích? |
📚 9.4. Các mẫu Structural Patterns phổ biến
Mẫu | Mô tả ngắn gọn |
---|---|
Adapter | Chuyển đổi giao diện để tương thích |
Bridge | Tách abstraction khỏi implementation |
Composite | Cấu trúc cây cho đối tượng phân cấp |
Decorator | Thêm tính năng cho object một cách linh hoạt |
Facade | Tạo giao diện đơn giản cho hệ thống phức tạp |
Flyweight | Tái sử dụng object giống nhau để tiết kiệm bộ nhớ |
Proxy | Đại diện cho object khác, kiểm soát truy cập |
🧩 9.5. Mối quan hệ với các nhóm còn lại
Nhóm | Mục tiêu chính |
---|---|
Creational Patterns | Tạo object hiệu quả, đúng cách |
Structural Patterns | Tổ chức hệ thống, quản lý mối quan hệ giữa class |
Behavioral Patterns | Xử lý tương tác & logic giữa object |
→ Structural là “trung tâm” – giúp kết nối cái được tạo ra (Creational) với cách chúng hoạt động (Behavioral).
🔧 9.6. Ví dụ thực tế áp dụng Structural Patterns
Bài toán | Mẫu áp dụng |
---|---|
Dùng class cũ trong giao diện mới | Adapter |
Giao diện đơn giản hóa hệ thống phức tạp | Facade |
Trang trí sản phẩm (pizza, đồ uống…) | Decorator |
Cấu trúc menu, thư mục, cây phân cấp | Composite |
Truy cập API thông qua bộ lọc kiểm tra | Proxy |
💬 Ghi nhớ chương này:
- Structural Patterns giúp bạn quản lý sự phức tạp bằng tổ chức tốt
- Là công cụ để:
- Giao tiếp rõ ràng giữa thành phần
- Kết hợp object/lớp mà không cần sửa nội dung bên trong
- Phù hợp với hệ thống lớn, cần tái sử dụng, tùy biến, và giảm phụ thuộc
🧱 Structural Patterns = Kiến trúc sư tổ chức ngôi nhà phần mềm.
Adapter Pattern
Khi giao diện không khớp nhưng logic vẫn dùng được
🎯 10.1. Mục tiêu của Adapter Pattern
Adapter Pattern giúp bạn chuyển đổi giao diện của một lớp cũ hoặc không tương thích thành một giao diện mà hệ thống hiện tại có thể sử dụng được.
Bạn cần mẫu này khi:
- Bạn có class cũ nhưng không thể sửa
- Bạn dùng thư viện bên ngoài có API không giống với hệ thống bạn đang phát triển
- Bạn muốn tái sử dụng logic mà không muốn (hoặc không thể) thay đổi cấu trúc gốc
🧠 10.2. Tư duy thực tế: ổ cắm điện
- Bạn có máy sấy tóc mua từ Mỹ (phích 3 chấu)
- Nhưng ổ điện ở Việt Nam là 2 chấu
→ Bạn không cần đổi máy sấy, chỉ cần một bộ chuyển đổi (adapter)
🧩 10.3. Cấu trúc tổng quát của Adapter Pattern
Thành phần | Vai trò |
---|---|
Target | Giao diện mà client mong muốn sử dụng |
Adaptee | Lớp có logic thực sự, nhưng giao diện không tương thích |
Adapter | Lớp trung gian – “chuyển đổi” từ Target sang Adaptee |
Client | Gọi đến Target (nhưng được xử lý bởi Adaptee qua Adapter) |
✍️ 10.4. Ví dụ Java – Adapter cho máy in PDF
Giao diện mong muốn (Target):
javaCopyEditinterface Printer {
void print(String content);
}
Lớp có sẵn (Adaptee):
javaCopyEditclass PdfGenerator {
public void generate(String data) {
System.out.println("PDF: " + data);
}
}
Adapter:
javaCopyEditclass PdfAdapter implements Printer {
private PdfGenerator pdfGen = new PdfGenerator();
public void print(String content) {
pdfGen.generate(content);
}
}
Sử dụng:
javaCopyEditPrinter printer = new PdfAdapter();
printer.print("Hello PDF"); // Gọi đúng interface, nhưng xử lý bên trong là PDF
🧾 10.5. Các kiểu Adapter
Kiểu Adapter | Cách triển khai |
---|---|
Object Adapter | Dùng composition – adapter chứa đối tượng gốc |
Class Adapter | Dùng kế thừa – adapter mở rộng từ adaptee (Java: không phổ biến vì không hỗ trợ đa kế thừa) |
✅ 10.6. Lợi ích của Adapter Pattern
Lợi ích | Giải thích |
---|---|
Tái sử dụng code cũ | Không cần sửa mã gốc |
Kết nối thư viện bên ngoài | Dễ tích hợp với hệ thống đang có |
Tách biệt giao diện và xử lý bên trong | Tăng tính module hóa |
Áp dụng Open/Closed Principle | Mở rộng mà không sửa lớp cũ |
⚠️ 10.7. Cảnh báo và hạn chế
Vấn đề | Hệ quả |
---|---|
Tạo quá nhiều adapter | Gây phức tạp, khó theo dõi |
Adapter quá dày | Làm giảm hiệu suất gọi hàm |
Không biết logic thật chạy ở đâu | Dễ khiến người mới hiểu sai luồng xử lý |
📚 10.8. Tình huống sử dụng thực tế
Tình huống | Adapter áp dụng thế nào? |
---|---|
Sử dụng class bên ngoài nhưng interface không khớp | Viết adapter cho thư viện |
Dùng module Cũ trong hệ thống mới | Adapter giữ nguyên code cũ |
Tích hợp API từ bên thứ ba | Adapter chuyển đổi đầu vào/ra |
💬 Ghi nhớ chương này:
- Adapter là cây cầu giữa giao diện bạn muốn và class bạn có
- Giúp bạn:
- Kết nối thư viện cũ với hệ thống mới
- Giữ lại logic gốc nhưng “đổi đầu vào”
- Làm việc với đa giao diện mà không lặp lại code
🔧 “Đừng vứt bỏ class không tương thích – hãy dùng Adapter để khiến nó hữu ích trở lại.”
Decorator Pattern
Gói thêm tính năng cho đối tượng mà không làm rối hệ thống
🎯 11.1. Mục tiêu của Decorator Pattern
Decorator cho phép bạn thêm tính năng mới cho một đối tượng đang hoạt động, mà không cần thay đổi code của lớp gốc.
- ✅ Vẫn giữ object cũ (không sửa đổi)
- ✅ Vẫn tuân thủ nguyên lý Open/Closed (mở rộng – không chỉnh sửa)
- ✅ Có thể bọc nhiều lớp Decorator chồng lên nhau
🧠 11.2. Vấn đề: kế thừa không đủ linh hoạt
Giả sử bạn có class Coffee
:
- Bạn muốn có
MilkCoffee
,SugarCoffee
,VanillaMilkSugarCoffee
…
Nếu dùng kế thừa:
javaCopyEditclass Coffee → MilkCoffee → MilkSugarCoffee → MilkSugarVanillaCoffee → ...
→ ❌ Quá nhiều class kết hợp → nổ tổ hợp
→ ❌ Không thể thay đổi động lúc runtime
🧩 11.3. Cấu trúc Decorator Pattern
Thành phần | Vai trò |
---|---|
Component (interface) | Giao diện chung |
ConcreteComponent | Lớp gốc (đối tượng chính) |
Decorator (abstract) | Lớp cha của các decorator – implement Component |
ConcreteDecorator | Lớp thêm tính năng cụ thể |
✍️ 11.4. Ví dụ Java: Trang trí cà phê
Giao diện:
javaCopyEditinterface Coffee {
String getDescription();
double getCost();
}
Cà phê gốc:
javaCopyEditclass SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}
Decorator trừu tượng:
javaCopyEditabstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee c) {
this.decoratedCoffee = c;
}
}
Thêm đường:
javaCopyEditclass SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee c) {
super(c);
}
public String getDescription() {
return decoratedCoffee.getDescription() + ", Sugar";
}
public double getCost() {
return decoratedCoffee.getCost() + 0.5;
}
}
Sử dụng:
javaCopyEditCoffee c = new SugarDecorator(new SimpleCoffee());
System.out.println(c.getDescription()); // Simple Coffee, Sugar
System.out.println(c.getCost()); // 2.5
→ Bạn có thể tiếp tục bọc bằng MilkDecorator
, VanillaDecorator
…
✅ 11.5. Ưu điểm của Decorator
Ưu điểm | Lợi ích thực tế |
---|---|
Linh hoạt – thêm tính năng lúc runtime | Có thể bật/tắt tùy ý |
Không thay đổi class gốc | Dễ bảo trì |
Tránh kế thừa rối rắm | Không cần tạo class tổ hợp |
Dễ kết hợp nhiều tính năng | Decorator lồng nhau được |
⚠️ 11.6. Nhược điểm và lưu ý
Hạn chế | Hệ quả |
---|---|
Tạo nhiều lớp Decorator nhỏ lẻ | Gây rối nếu không đặt tên rõ ràng |
Gọi get() quá nhiều tầng | Gây khó debug hoặc trace log |
Không phù hợp với object đơn giản | Dùng builder có thể hiệu quả hơn |
📚 11.7. Tình huống thực tế
Tình huống | Dùng Decorator để… |
---|---|
UI Button có border, shadow, hover | Gói từng hiệu ứng riêng |
Đồ uống (cà phê, trà, pizza…) | Trang trí thêm topping |
Dữ liệu: mã hóa, nén, log | Bọc từng tầng xử lý riêng |
Logging hoặc security layer | Bọc quanh service thật |
🔄 11.8. Decorator vs Inheritance
So sánh | Decorator | Kế thừa class |
---|---|---|
Linh hoạt runtime | ✅ | ❌ |
Nổ tổ hợp class | ❌ (1 class Decorator dùng nhiều lần) | ✅ |
Dễ thay đổi, bật/tắt | ✅ | ❌ |
Đơn giản cho object nhỏ | ❌ (nhiều lớp) | ✅ |
💬 Ghi nhớ chương này:
- Decorator = “bọc thêm tính năng cho object mà không chạm vào class gốc”
- Phù hợp khi:
- Object có nhiều “topping” (tính năng phụ trợ)
- Cần bật/tắt tính năng động tại runtime
- Rất mạnh cho UI, dịch vụ, bảo mật, log, dữ liệu
🎁 Decorator không phá cấu trúc – nó “trang trí” đúng cách cho object bạn đang có.
Composite Pattern
Thiết kế hệ thống phân cấp dạng cây một cách đồng nhất
🎯 12.1. Vấn đề: Đối tượng phân cấp phức tạp và khó quản lý
Giả sử bạn xây dựng:
- Một hệ thống menu đa cấp (Menu → Submenu → Item)
- Một tổ chức công ty (Giám đốc → Trưởng phòng → Nhân viên)
- Một cây thư mục (Folder → Subfolder → File)
Bạn cần xử lý tất cả phần tử trong cấu trúc đó theo cách giống nhau:
Ví dụ:
menu.render()
nên hoạt động với cảitem
lẫnsubmenu
chứa nhiềuitem
.
→ Nếu dùng if-else
, bạn sẽ phân biệt từng loại đối tượng và logic xử lý rất rối.
✅ 12.2. Mục tiêu của Composite Pattern
Composite Pattern giúp bạn xử lý cấu trúc cây gồm các object có thể lồng nhau, và cho phép dùng cùng một interface để thao tác với tất cả phần tử.
🧠 12.3. Ý tưởng: “Thành phần và tập hợp đều cùng một loại”
File
là một thành phầnFolder
là một tập hợp chứa nhiều thành phần- Nhưng cả hai đều có thể được gọi
getSize()
,render()
,delete()
… như nhau
→ Bạn không cần biết mình đang xử lý gì – chỉ cần gọi đúng interface.
📦 12.4. Cấu trúc tổng quát Composite Pattern
Thành phần | Vai trò |
---|---|
Component | Giao diện chung (hàm add() , display() …) |
Leaf | Đối tượng con đơn lẻ (không chứa gì) |
Composite | Đối tượng chứa thành phần khác (danh sách Component ) |
Client | Gọi tới interface Component và xử lý tất cả như nhau |
✍️ 12.5. Ví dụ Java: Cây thư mục
Giao diện chung:
javaCopyEditinterface FileSystem {
void ls();
}
File đơn lẻ (Leaf):
javaCopyEditclass File implements FileSystem {
private String name;
public File(String name) {
this.name = name;
}
public void ls() {
System.out.println("File: " + name);
}
}
Folder chứa file (Composite):
javaCopyEditclass Folder implements FileSystem {
private String name;
private List<FileSystem> children = new ArrayList<>();
public Folder(String name) {
this.name = name;
}
public void add(FileSystem f) {
children.add(f);
}
public void ls() {
System.out.println("Folder: " + name);
for (FileSystem child : children) {
child.ls();
}
}
}
Sử dụng:
javaCopyEditFolder root = new Folder("root");
File a = new File("a.txt");
Folder sub = new Folder("sub");
sub.add(new File("b.txt"));
root.add(a);
root.add(sub);
root.ls();
📊 12.6. Lợi ích của Composite Pattern
Lợi ích | Giải thích |
---|---|
Giao diện đồng nhất | Dù là File hay Folder , bạn chỉ cần gọi ls() |
Dễ mở rộng | Có thể thêm loại mới như Shortcut , ImageFolder … |
Không cần instanceof , if-else | Giảm phân nhánh logic |
Phù hợp cho cấu trúc phân cấp | Menu, tổ chức, UI component, v.v. |
⚠️ 12.7. Cảnh báo khi dùng Composite
Hạn chế | Gợi ý |
---|---|
Có thể khó debug cây sâu | In log phân cấp hoặc sử dụng trình debug |
Không hợp nếu không có lồng nhau | Chỉ dùng nếu bạn thực sự có cấu trúc phân cấp |
Có thể rối nếu client vẫn phân biệt Leaf/Composite | Đảm bảo client chỉ xử lý qua Component |
📚 12.8. Ứng dụng thực tế
Tình huống | Composite giúp gì? |
---|---|
Menu UI đa cấp | Render giống nhau cho từng menu item |
Tổ chức nhân sự | Lệnh getSalary() dùng được cho cả nhân viên và quản lý |
File explorer, cây thư mục | Gọi delete() hoặc download() cho folder hay file đều như nhau |
Cây câu hỏi, form động | Mỗi thành phần là 1 node có thể chứa phần tử con |
💬 Ghi nhớ chương này:
- Composite giúp bạn dùng một kiểu dữ liệu duy nhất để quản lý cả cấu trúc phức hợp và thành phần đơn
- Rất phù hợp cho:
- UI lồng nhau
- Cây dữ liệu
- Sơ đồ tổ chức
- Ưu điểm: gọn gàng, dễ mở rộng, dễ lặp, không cần phân biệt kiểu thủ công
🌲 Composite Pattern = “Xem phần và tổng thể như nhau”.
Facade Pattern
Giấu đi phức tạp, lộ ra đơn giản
🎯 13.1. Vấn đề thực tế
Bạn có một hệ thống với nhiều thành phần:
- Subsystem A, B, C, D…
- Mỗi subsystem có hàng loạt class, method, bước xử lý…
Nếu để client gọi trực tiếp:
javaCopyEdita.init();
b.validate();
c.load();
d.finalizeProcess();
→ ❌ Quá phức tạp
→ ❌ Khó sử dụng lại
→ ❌ Khó bảo trì khi các bước thay đổi
✅ 13.2. Mục tiêu của Facade Pattern
Facade Pattern cung cấp một giao diện đơn giản và thống nhất, giúp client dễ sử dụng một hệ thống phức tạp mà không cần hiểu chi tiết nội bộ.
🧠 13.3. Hình ảnh thực tế: Lễ tân khách sạn
- Bạn không cần trực tiếp làm việc với từng bộ phận (buồng phòng, kế toán, kỹ thuật…)
- Bạn chỉ cần nói với lễ tân, họ sẽ xử lý tất cả phía sau.
→ Lễ tân = Facade
→ Các bộ phận trong khách sạn = Subsystems
🧱 13.4. Cấu trúc tổng quát
Thành phần | Vai trò |
---|---|
Facade | Giao diện đơn giản cho client |
Subsystems | Các lớp, module thực hiện công việc thật sự |
Client | Gọi Facade, không gọi trực tiếp subsystem |
✍️ 13.5. Ví dụ Java – Facade cho hệ thống giải trí tại gia
Các subsystem:
javaCopyEditclass Amplifier {
public void on() { System.out.println("Amp on"); }
}
class DVDPlayer {
public void play(String movie) {
System.out.println("Playing movie: " + movie);
}
}
class Lights {
public void dim() { System.out.println("Lights dimmed"); }
}
Facade:
javaCopyEditclass HomeTheaterFacade {
private Amplifier amp;
private DVDPlayer dvd;
private Lights lights;
public HomeTheaterFacade(Amplifier amp, DVDPlayer dvd, Lights lights) {
this.amp = amp;
this.dvd = dvd;
this.lights = lights;
}
public void watchMovie(String movie) {
lights.dim();
amp.on();
dvd.play(movie);
}
}
Sử dụng:
javaCopyEditHomeTheaterFacade home = new HomeTheaterFacade(new Amplifier(), new DVDPlayer(), new Lights());
home.watchMovie("Inception");
→ Client chỉ gọi 1 hàm duy nhất thay vì thao tác với 3–4 subsystem.
📈 13.6. Lợi ích của Facade Pattern
Lợi ích | Mô tả |
---|---|
Giao diện gọn gàng cho hệ thống lớn | Client không cần biết nội bộ subsystem |
Dễ sử dụng lại | Dễ viết API, wrapper, CLI |
Giảm phụ thuộc | Giữa client và logic bên trong |
Có thể kết hợp với pattern khác | Facade kết hợp tốt với Singleton, Adapter |
⚠️ 13.7. Nhược điểm và cảnh báo
Hạn chế | Cảnh báo |
---|---|
Che giấu quá nhiều logic nội bộ | Làm client “mù mờ” nếu cần xử lý chi tiết |
Không thay thế được thiết kế kém | Facade không sửa được hệ thống phức tạp vô lý |
Có thể bị lạm dụng thành “God class” | Nếu chứa quá nhiều phương thức không liên quan |
📚 13.8. Tình huống sử dụng thực tế
Tình huống | Facade giúp gì? |
---|---|
Giao diện lập trình với hệ thống cũ | Tạo lớp trung gian API gọn gàng |
Giao diện web gọi nhiều service | Dùng controller gọn gọi nhiều service logic |
Module CLI hoặc REST API trung gian | Tạo lệnh đơn giản → gọi nhiều lớp xử lý |
Dự án chia team: Frontend – Backend | Backend cung cấp Facade → không cần biết chi tiết DAO |
💬 Ghi nhớ chương này:
- Facade Pattern = “cửa chính dễ dùng để bước vào một ngôi nhà phức tạp”
- Dùng khi:
- Hệ thống đã ổn định nhưng quá rối để dùng trực tiếp
- Bạn cần cung cấp API hoặc giao diện rõ ràng
- Giúp tăng:
- Tính đóng gói
- Dễ dùng lại
- Dễ bảo trì
🎯 Facade không sửa thiết kế xấu – nhưng nó giúp bạn ẩn nó khỏi client.
Proxy Pattern
Bảo vệ, trì hoãn hoặc điều hướng truy cập đến đối tượng thật
🎯 14.1. Vấn đề: Không phải lúc nào cũng nên truy cập trực tiếp đối tượng thật
Ví dụ:
- Bạn có một object kết nối cơ sở dữ liệu → không muốn khởi tạo ngay lập tức
- Hoặc một tài nguyên nhạy cảm → chỉ user được phân quyền mới được truy cập
- Hoặc một dịch vụ từ xa → cần đóng gói giao tiếp (như API, RPC)
→ Truy cập trực tiếp là nguy hiểm, tốn tài nguyên, khó kiểm soát
✅ 14.2. Mục tiêu của Proxy Pattern
Proxy Pattern cung cấp một đối tượng thay thế (proxy) cho đối tượng thật, cho phép:
- Kiểm soát quyền truy cập
- Trì hoãn khởi tạo
- Ghi log, theo dõi
- Đóng gói gọi từ xa (remote call)
🧠 14.3. Proxy ≠ Adapter
Mẫu | Mục tiêu |
---|---|
Adapter | Làm giao diện không tương thích → tương thích |
Proxy | Kiểm soát hành vi truy cập, giả dạng đối tượng thật |
🧱 14.4. Cấu trúc tổng quát
Thành phần | Vai trò |
---|---|
Subject | Interface chung cho cả Proxy và RealSubject |
RealSubject | Đối tượng thật (có logic xử lý) |
Proxy | Lớp thay thế, kiểm soát truy cập đến RealSubject |
Client | Gọi qua Proxy như gọi object thật |
✍️ 14.5. Ví dụ Java – Proxy cho hình ảnh
Interface:
javaCopyEditinterface Image {
void display();
}
Hình ảnh thật:
javaCopyEditclass RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("Loading " + filename);
}
public void display() {
System.out.println("Displaying " + filename);
}
}
Proxy:
javaCopyEditclass ProxyImage implements Image {
private RealImage realImage;
private String filename;
public ProxyImage(String filename) {
this.filename = filename;
}
public void display() {
if (realImage == null) {
realImage = new RealImage(filename); // Trì hoãn tải ảnh
}
realImage.display();
}
}
Sử dụng:
javaCopyEditImage img = new ProxyImage("big_photo.jpg");
img.display(); // lần đầu: load + display
img.display(); // lần 2: chỉ display
📚 14.6. Các loại Proxy phổ biến
Loại Proxy | Công dụng |
---|---|
Virtual Proxy | Trì hoãn khởi tạo đối tượng tốn tài nguyên |
Protection Proxy | Kiểm soát quyền truy cập (authorization) |
Remote Proxy | Đóng gói truy cập object ở nơi khác (API, RPC) |
Smart Proxy | Ghi log, đếm số lần truy cập, cache, validate |
✅ 14.7. Lợi ích của Proxy Pattern
Lợi ích | Giải thích |
---|---|
Kiểm soát truy cập | Hạn chế ai có quyền dùng object thật |
Tối ưu hiệu suất | Trì hoãn hoặc dùng cache |
Tăng tính mở rộng | Có thể ghi log, bảo vệ mà không sửa object chính |
Dễ thay thế / hoán đổi object | Vì proxy có cùng interface |
⚠️ 14.8. Nhược điểm và lưu ý
Hạn chế | Hậu quả |
---|---|
Gọi chậm hơn object gốc | Vì qua thêm một lớp |
Rối khi có nhiều loại proxy | Khó debug và bảo trì |
Proxy có thể “ẩn” logic | Client không biết thực sự đang gọi ai |
📦 14.9. Ứng dụng thực tế của Proxy
Tình huống | Áp dụng |
---|---|
Giao diện hiển thị ảnh, video nặng | Virtual Proxy |
Truy cập file, database theo user | Protection Proxy |
Gọi dịch vụ từ xa (REST, gRPC) | Remote Proxy |
Xác thực tài khoản | Proxy login/auth |
Ghi log mọi lệnh gọi đến class | Smart Proxy |
💬 Ghi nhớ chương này:
- Proxy = “đại diện có kiểm soát” cho đối tượng thật
- Hữu ích khi:
- Không muốn khởi tạo object ngay lập tức
- Cần kiểm soát quyền truy cập
- Cần đóng gói logic từ xa hoặc logic phụ trợ
🛡 Proxy không thay đổi logic chính – nó bảo vệ, trì hoãn, hoặc giám sát nó.
Nhóm Behavioral Patterns (Mẫu Thiết Kế Hành Vi)
🎯 15.1. Mục tiêu của nhóm Behavioral Patterns
Nhóm Behavioral Patterns tập trung vào việc quản lý luồng tương tác, giao tiếp và trách nhiệm giữa các đối tượng – không phải chúng được tạo ra như thế nào, mà là chúng hành xử ra sao.
🤹 15.2. Tại sao cần nhóm này?
Trong phần mềm thực tế:
- Các đối tượng không làm việc độc lập, mà phối hợp với nhau
- Cần quản lý:
- Luồng dữ liệu (ai gọi ai, theo thứ tự nào)
- Phân quyền hành động (ai chịu trách nhiệm)
- Giao tiếp nhiều-đến-một (observer)
- Hoán đổi hành vi linh hoạt (strategy)
→ Bạn không muốn các đối tượng gắn chặt vào nhau, vì:
- Khó test
- Khó mở rộng
- Dễ gây lỗi domino nếu sửa một chỗ
📚 15.3. Các mẫu thuộc nhóm Behavioral Patterns
Mẫu | Chức năng chính |
---|---|
Observer | Một thay đổi → nhiều đối tượng được thông báo |
Strategy | Hoán đổi thuật toán linh hoạt tại runtime |
Command | Biến hành động thành đối tượng độc lập |
Template Method | Xác định khung xử lý chung, cho phép lớp con điều chỉnh chi tiết |
State | Thay đổi hành vi theo trạng thái nội tại |
Chain of Responsibility | Gửi yêu cầu qua chuỗi xử lý |
Iterator | Duyệt các phần tử mà không lộ cấu trúc |
Mediator | Trung tâm điều phối giao tiếp giữa object |
Visitor | Thêm hành vi mới cho object mà không thay đổi class |
Interpreter | Định nghĩa cú pháp và diễn giải ngôn ngữ đơn giản |
Memento | Lưu và phục hồi trạng thái object (undo/redo) |
🧭 15.4. Mục tiêu lớn nhất: Tách “ai gọi” và “ai thực hiện”
Các mẫu hành vi giúp:
- Tách logic điều phối ra khỏi logic xử lý
- Thay vì
A
gọi trực tiếpB
, ta:- Giao tiếp qua
Mediator
,Command
,Observer
… - Hoặc truyền hành vi vào
A
(Strategy)
- Giao tiếp qua
📌 15.5. Ứng dụng thực tế của nhóm này
Tình huống | Mẫu phù hợp |
---|---|
Giao diện thay đổi khi model thay đổi | Observer |
Nút bấm trong giao diện | Command |
Nhiều thuật toán lọc sản phẩm khác nhau | Strategy |
Undo/Redo tài liệu | Memento |
Giao tiếp phức tạp trong game | Mediator |
Duyệt cây thư mục, form nhiều bước | Iterator, State |
🧠 15.6. Các mẫu hành vi thường kết hợp tốt với Structural & Creational
Ví dụ:
- Observer + Composite → xử lý thay đổi trong cây UI
- Command + Singleton → thực thi lệnh trung tâm
- Strategy + Factory → tạo thuật toán theo cấu hình
💬 Ghi nhớ chương này:
- Behavioral Patterns giúp hệ thống linh hoạt hơn khi phối hợp và xử lý hành vi
- Giảm ràng buộc giữa object
- Dễ test, dễ thay đổi
- Giữ đúng nguyên lý SOLID, đặc biệt là:
- Open/Closed Principle
- Single Responsibility Principle
🤹 Mẫu hành vi không tạo thêm object – nó tạo thêm trật tự.
Observer Pattern – Một thay đổi, nhiều phản ứng
🎯 16.1. Mục tiêu của Observer Pattern
Cho phép một đối tượng (Subject) thông báo đến nhiều đối tượng khác (Observer) khi có sự thay đổi, mà không cần biết chi tiết ai đang lắng nghe.
Ứng dụng cực mạnh trong:
- Giao diện UI
- Mô hình MVC
- Sự kiện hệ thống
- Pub/Sub, socket, signal, thông báo real-time
🧠 16.2. Ý tưởng: đăng ký và tự động nhận thông báo
- Giống như bạn bấm nút “Theo dõi” kênh YouTube
- Khi có video mới, kênh đó không cần gọi bạn – hệ thống gửi thông báo tự động đến tất cả người theo dõi
→ Observer = Người theo dõi
→ Subject = Nguồn phát sự kiện
🧩 16.3. Cấu trúc tổng quát
Thành phần | Vai trò |
---|---|
Subject | Giao diện cho việc thêm/xoá/thông báo observer |
ConcreteSubject | Đối tượng thật, chứa dữ liệu và gọi notify() |
Observer | Giao diện lắng nghe |
ConcreteObserver | Đối tượng thực tế thực thi cập nhật |
✍️ 16.4. Ví dụ Java – Cảnh báo nhiệt độ
Subject (sensor nhiệt độ):
javaCopyEditinterface Subject {
void addObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
Observer:
javaCopyEditinterface Observer {
void update(double temperature);
}
Cảm biến nhiệt:
javaCopyEditclass TemperatureSensor implements Subject {
private List<Observer> observers = new ArrayList<>();
private double temperature;
public void setTemperature(double temp) {
this.temperature = temp;
notifyObservers();
}
public void addObserver(Observer o) { observers.add(o); }
public void removeObserver(Observer o) { observers.remove(o); }
public void notifyObservers() {
for (Observer o : observers) {
o.update(temperature);
}
}
}
Màn hình hiển thị:
javaCopyEditclass Display implements Observer {
public void update(double temp) {
System.out.println("Nhiệt độ mới: " + temp + "°C");
}
}
Sử dụng:
javaCopyEditTemperatureSensor sensor = new TemperatureSensor();
Display screen = new Display();
sensor.addObserver(screen);
sensor.setTemperature(30.5); // In: Nhiệt độ mới: 30.5°C
✅ 16.5. Lợi ích của Observer Pattern
Lợi ích | Giải thích |
---|---|
Tách biệt giữa phát & nhận thông tin | Không cần biết ai đang nghe |
Dễ mở rộng | Thêm/xoá listener không ảnh hưởng Subject |
Dễ test, module hoá | Mỗi Observer hoạt động độc lập |
Giao tiếp 1 → N tự nhiên | Không cần lặp code notify thủ công |
⚠️ 16.6. Nhược điểm & lưu ý
Nhược điểm | Cảnh báo |
---|---|
Không kiểm soát thứ tự gọi Observer | Có thể cần dùng queue nếu thứ tự quan trọng |
Quá nhiều Observer → hiệu suất giảm | Dùng batch, thread-safe |
Vòng lặp vô hạn nếu notify lẫn nhau | Cẩn thận với Observer gọi lại Subject |
📦 16.7. Ứng dụng thực tế
Tình huống | Cách áp dụng |
---|---|
UI thay đổi theo model (MVC) | View là Observer, Model là Subject |
Event System trong game | Enemy → Player → UI phản ứng |
Đa màn hình dashboard | Cập nhật dữ liệu từ 1 nguồn trung tâm |
WebSocket, Redis pub/sub, Kafka… | Toàn bộ dùng pattern Observer! |
💬 Ghi nhớ chương này:
- Observer = “lắng nghe không đồng bộ”
- Cần khi:
- Một thay đổi → nhiều hệ quả
- Bạn muốn decouple giữa data & hiển thị
- Bạn cần hệ thống thông báo real-time
📡 Observer Pattern là cốt lõi của sự kiện, MVC, và hệ thống phân tán.
Strategy Pattern – Hoán đổi thuật toán linh hoạt
🎯 17.1. Vấn đề thực tế: nhiều thuật toán, không muốn if-else
Giả sử bạn có một ứng dụng:
- Tính phí giao hàng: theo khoảng cách, khối lượng, thời gian
- Mỗi trường hợp cần thuật toán khác nhau
Cách tệ nhất là:
javaCopyEditif (type == "FAST") { ... }
else if (type == "STANDARD") { ... }
else if (type == "CHEAP") { ... }
→ ❌ Khó mở rộng
→ ❌ Vi phạm nguyên lý Open/Closed
→ ❌ Rối rắm nếu có 10 loại thuật toán
✅ 17.2. Mục tiêu của Strategy Pattern
Cho phép bạn đóng gói các thuật toán thành các class riêng biệt, và hoán đổi chúng linh hoạt trong runtime.
- Client không cần biết thuật toán bên trong
- Chỉ cần “giao diện tính toán” + inject thuật toán phù hợp
🧩 17.3. Cấu trúc tổng quát
Thành phần | Vai trò |
---|---|
Strategy (interface) | Giao diện cho các thuật toán |
ConcreteStrategy | Các thuật toán cụ thể |
Context | Class chính dùng Strategy |
Client | Gán strategy phù hợp cho context |
✍️ 17.4. Ví dụ Java – Tính phí vận chuyển
Strategy:
javaCopyEditinterface ShippingStrategy {
double calculateFee(double weight);
}
Các thuật toán cụ thể:
javaCopyEditclass FastShipping implements ShippingStrategy {
public double calculateFee(double weight) {
return 10 + weight * 1.5;
}
}
class StandardShipping implements ShippingStrategy {
public double calculateFee(double weight) {
return 5 + weight * 1.0;
}
}
Context:
javaCopyEditclass ShippingCalculator {
private ShippingStrategy strategy;
public void setStrategy(ShippingStrategy s) {
this.strategy = s;
}
public double calculate(double weight) {
return strategy.calculateFee(weight);
}
}
Sử dụng:
javaCopyEditShippingCalculator calc = new ShippingCalculator();
calc.setStrategy(new FastShipping());
System.out.println(calc.calculate(2)); // 13.0
calc.setStrategy(new StandardShipping());
System.out.println(calc.calculate(2)); // 7.0
✅ 17.5. Lợi ích của Strategy Pattern
Lợi ích | Giải thích |
---|---|
Không cần if-else chọn thuật toán | Dễ thêm loại mới |
Mỗi thuật toán nằm riêng một class | Tăng tính module |
Hoán đổi runtime dễ dàng | Dùng được với UI, cấu hình người dùng |
Dễ test riêng từng strategy | Unit test độc lập |
⚠️ 17.6. Cảnh báo khi dùng Strategy
Vấn đề | Gợi ý |
---|---|
Quá nhiều class nhỏ lẻ | Gom chung nếu đơn giản |
Client truyền nhầm strategy | Cân nhắc dùng Factory kèm Strategy |
Không cần thiết nếu chỉ có 1–2 thuật toán | Dùng kế thừa hoặc lambda |
📚 17.7. Tình huống thực tế
Tình huống | Áp dụng Strategy để… |
---|---|
Tính khuyến mãi trong giỏ hàng | Mỗi loại giảm giá là 1 strategy |
Sắp xếp dữ liệu trong bảng | Sort by Name, Price, Date… |
Tính điểm bài thi | Theo từng loại học sinh/đề thi |
Game: hành vi tấn công/quái vật | Mỗi loại AI là 1 strategy |
Máy học: chọn thuật toán dự đoán | Tách model thành strategy |
🔁 17.8. So sánh Strategy vs Template Method
Tiêu chí | Strategy | Template Method |
---|---|---|
Cách chọn hành vi | Truyền vào object khác (composition) | Ghi đè method trong lớp con (inheritance) |
Runtime hay compile | Runtime | Compile-time |
Thêm logic mới | Dễ (thêm class strategy) | Cần subclass |
💬 Ghi nhớ chương này:
- Strategy Pattern giúp bạn gỡ bỏ đống if-else chọn thuật toán
- Phù hợp khi:
- Có nhiều thuật toán cùng chức năng
- Muốn chọn theo user/config/runtime
- Muốn kiểm thử từng thuật toán riêng
🔧 “Cắm” thuật toán khác nhau mà không cần sửa logic gọi – chính là tinh thần Strategy.
Command Pattern
Biến hành động thành đối tượng, mẫu cực kỳ quan trọng trong nhóm Behavioral Patterns.
🎯 18.1. Vấn đề: Không muốn gọi phương thức trực tiếp
Giả sử bạn xây dựng hệ thống:
- Giao diện có nhiều nút: Undo, Redo, Save, Load
- Mỗi nút thực hiện một hành động khác nhau
- Bạn muốn:
✅ Gán hành động động tại runtime
✅ Hủy (undo) hành động
✅ Ghi log, xếp hàng hành động
→ Việc gọi trực tiếp object.method()
sẽ không đủ linh hoạt và khó điều khiển.
✅ 18.2. Mục tiêu của Command Pattern
Biến một hành động (ví dụ: gọi
save()
) thành một đối tượng riêng biệt, giúp bạn:
- Trì hoãn, log, queue, undo hành động
- Gán lại hành vi cho nút, phím tắt, hoặc hệ thống UI khác
🧱 18.3. Cấu trúc tổng quát
Thành phần | Vai trò |
---|---|
Command (interface) | Giao diện chung với hàm execute() |
ConcreteCommand | Thực thi logic cụ thể |
Receiver | Đối tượng thật thực hiện hành động |
Invoker | Gọi lệnh (UI, controller, macro) |
Client | Tạo Command và gán cho Invoker |
✍️ 18.4. Ví dụ Java – Remote điều khiển đèn
Interface:
javaCopyEditinterface Command {
void execute();
}
Receiver:
javaCopyEditclass Light {
public void on() {
System.out.println("Đèn bật");
}
public void off() {
System.out.println("Đèn tắt");
}
}
ConcreteCommand:
javaCopyEditclass LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.on();
}
}
Invoker (remote control):
javaCopyEditclass RemoteControl {
private Command slot;
public void setCommand(Command command) {
this.slot = command;
}
public void pressButton() {
slot.execute();
}
}
Sử dụng:
javaCopyEditLight light = new Light();
Command lightOn = new LightOnCommand(light);
RemoteControl remote = new RemoteControl();
remote.setCommand(lightOn);
remote.pressButton(); // Output: Đèn bật
✅ 18.5. Lợi ích của Command Pattern
Lợi ích | Giải thích |
---|---|
Gán lại hành động cho UI linh hoạt | Không gắn cứng vào hàm cụ thể |
Trì hoãn hoặc queue hành động | Dùng cho undo/redo, xử lý đa luồng |
Ghi log hành động dễ dàng | Mỗi hành động là 1 object riêng |
Tuân thủ nguyên lý Single Responsibility | Tách UI, logic gọi và xử lý ra riêng |
⚠️ 18.6. Cảnh báo khi dùng
Hạn chế | Lưu ý |
---|---|
Nhiều class nhỏ (mỗi command 1 class) | Có thể dùng lambda (Java 8+), anonymous class |
Khó trace lỗi nếu lệnh bị queue nhiều | Cần log/trace rõ |
Dễ rối nếu gộp command với Receiver | Tách riêng trách nhiệm cho dễ test, bảo trì |
📚 18.7. Tình huống sử dụng thực tế
Tình huống | Command giúp gì? |
---|---|
Nút Undo/Redo trong editor | Ghi từng hành động làm Command, lưu lại stack |
Giao diện nút bấm, menu động | Dễ gán/hoán đổi hành vi |
Macro trong game | Ghi chuỗi lệnh để phát lại |
Quản lý hàng đợi xử lý email | Command queue |
Lập lịch tác vụ (scheduler) | Command + delay |
🔁 18.8. So sánh Strategy vs Command
Tiêu chí | Strategy | Command |
---|---|---|
Mục tiêu chính | Hoán đổi thuật toán | Biến hành động thành object |
Có lưu được state? | ❌ Không | ✅ Có |
Có undo được không? | ❌ (khó) | ✅ (rất tốt) |
Thường dùng với UI? | ❌ | ✅ (nút, phím tắt…) |
💬 Ghi nhớ chương này:
- Command Pattern giúp bạn kiểm soát hành động như một “đơn vị độc lập”
- Phù hợp khi:
- Cần undo, redo
- Giao diện nhiều nút, keybinding
- Hành động cần log, queue hoặc lưu trữ lại
- Có thể kết hợp với:
- Memento để undo
- Macro để ghi hàng loạt hành động
🧠 Bạn không chỉ gọi method – bạn biến nó thành object để điều khiển như bạn muốn.
Template Method Pattern
Khung xử lý chung, tuỳ chỉnh chi tiết bằng kế thừa
🎯 19.1. Vấn đề: Khi quy trình tổng thể giống nhau, nhưng chi tiết mỗi bước khác nhau
Ví dụ: hệ thống xuất báo cáo PDF và báo cáo Excel đều trải qua quy trình:
- Chuẩn bị dữ liệu
- Xử lý logic báo cáo
- Định dạng
- Xuất file
→ Các bước giống nhau, nhưng:
- Bước 3 và 4 khác nhau tuỳ loại file
- Bạn muốn giữ khung cố định, chỉ cho phép override phần cần tùy chỉnh
✅ 19.2. Mục tiêu của Template Method Pattern
Cho phép định nghĩa khung xử lý chung (template) trong lớp cha, và cho phép lớp con override một phần các bước để thay đổi hành vi cụ thể.
→ Lớp con không thể thay đổi toàn bộ quy trình, chỉ được can thiệp vào những điểm được phép.
🧱 19.3. Cấu trúc tổng quát
Thành phần | Vai trò |
---|---|
AbstractClass | Định nghĩa template method (final ), và các bước (step1() , step2() ) có thể abstract hoặc default |
ConcreteClass | Ghi đè các bước cụ thể |
Client | Gọi đến templateMethod() |
✍️ 19.4. Ví dụ Java – Xuất báo cáo
Lớp cha:
javaCopyEditabstract class ReportGenerator {
public final void generateReport() {
fetchData();
processData();
formatReport();
export();
}
protected void fetchData() {
System.out.println("Lấy dữ liệu từ DB");
}
protected abstract void processData();
protected abstract void formatReport();
protected abstract void export();
}
Lớp con: PDF
javaCopyEditclass PDFReport extends ReportGenerator {
protected void processData() {
System.out.println("Xử lý dữ liệu cho PDF");
}
protected void formatReport() {
System.out.println("Định dạng PDF");
}
protected void export() {
System.out.println("Xuất file PDF");
}
}
Lớp con: Excel
javaCopyEditclass ExcelReport extends ReportGenerator {
protected void processData() {
System.out.println("Xử lý dữ liệu cho Excel");
}
protected void formatReport() {
System.out.println("Định dạng bảng Excel");
}
protected void export() {
System.out.println("Xuất file .xlsx");
}
}
Sử dụng:
javaCopyEditReportGenerator report = new PDFReport();
report.generateReport();
✅ 19.5. Lợi ích của Template Method
Lợi ích | Giải thích |
---|---|
Đảm bảo quy trình nhất quán | Vì khung được cố định trong lớp cha |
Cho phép tuỳ chỉnh có kiểm soát | Lớp con chỉ override phần được phép |
Tăng khả năng tái sử dụng | Khung dùng lại nhiều lần |
Dễ mở rộng | Chỉ cần thêm lớp con là có chức năng mới |
⚠️ 19.6. Nhược điểm & lưu ý
Hạn chế | Gợi ý |
---|---|
Ràng buộc theo kế thừa | Không linh hoạt như Strategy (dùng composition) |
Có thể dẫn đến “God class” nếu quá nhiều bước | Cần chia nhỏ quy trình hợp lý |
Không hỗ trợ runtime thay đổi | Chỉ chọn được hành vi tại compile-time |
🔁 19.7. So sánh Template Method vs Strategy
Tiêu chí | Template Method | Strategy |
---|---|---|
Kiểu mở rộng | Kế thừa | Composition (thành phần) |
Runtime chọn hành vi | ❌ Không | ✅ Có |
Linh hoạt hơn | ❌ | ✅ |
Kiểm soát chặt chẽ | ✅ Quy trình khó bị phá vỡ | ❌ Dễ bị thay đổi không kiểm soát |
📚 19.8. Tình huống thực tế
Tình huống | Mẫu này áp dụng tốt |
---|---|
Xuất nhiều loại báo cáo | ✅ Template Method |
Gửi email với các định dạng khác nhau | ✅ Template Method |
Chuỗi bước xử lý cố định trong game | ✅ Template Method |
Xử lý request trong middleware | ✅ (Spring Web, Laravel middleware…) |
💬 Ghi nhớ chương này:
- Template Method = khung xử lý cố định + điểm mở cho mở rộng
- Lớp con có thể:
- Ghi đè bước cụ thể
- Không phá vỡ cấu trúc tổng thể
- Dễ tổ chức quy trình xử lý có cấu trúc
🧱 Nếu bạn có quy trình “bất biến trong tổng thể – biến động trong chi tiết”, hãy dùng Template Method.
State Pattern
Khi trạng thái thay đổi, hành vi cũng thay đổi
🎯 20.1. Vấn đề thực tế: hành vi thay đổi theo trạng thái
Ví dụ:
- Máy ATM:
- Khi đang chờ thẻ → chỉ chấp nhận
insertCard()
- Khi đã có thẻ → chấp nhận
enterPin()
- Khi rút tiền → không cho nạp thẻ nữa
- Khi đang chờ thẻ → chỉ chấp nhận
Nếu xử lý bằng if...else
theo trạng thái:
javaCopyEditif (state == WAITING_CARD) { ... }
else if (state == PIN_ENTERED) { ... }
else if (state == WITHDRAWING) { ... }
→ ❌ Rối rắm, khó mở rộng
→ ❌ Vi phạm nguyên lý đóng/mở
✅ 20.2. Mục tiêu của State Pattern
Cho phép một object thay đổi hành vi tương ứng với trạng thái nội tại, bằng cách ủy quyền hành vi cho các lớp trạng thái cụ thể.
- Không cần
if/else
dài dòng - Mỗi trạng thái là một class riêng
- Object “hoạt động như thể nó thay đổi class”
🧩 20.3. Cấu trúc tổng quát
Thành phần | Vai trò |
---|---|
Context | Đối tượng chính, có trạng thái nội tại (hiện tại) |
State (interface) | Giao diện cho các hành vi cần override |
ConcreteState | Mỗi trạng thái cụ thể, triển khai logic riêng |
Client | Tương tác với Context – không cần biết state cụ thể |
✍️ 20.4. Ví dụ Java – Máy ATM
Giao diện trạng thái:
javaCopyEditinterface ATMState {
void insertCard();
void enterPin();
void withdraw();
}
Context:
javaCopyEditclass ATMContext {
private ATMState currentState;
public void setState(ATMState state) {
this.currentState = state;
}
public void insertCard() {
currentState.insertCard();
}
public void enterPin() {
currentState.enterPin();
}
public void withdraw() {
currentState.withdraw();
}
}
Một trạng thái cụ thể:
javaCopyEditclass NoCardState implements ATMState {
private ATMContext atm;
public NoCardState(ATMContext atm) {
this.atm = atm;
}
public void insertCard() {
System.out.println("Thẻ được chèn vào.");
atm.setState(new HasCardState(atm));
}
public void enterPin() {
System.out.println("Chưa có thẻ.");
}
public void withdraw() {
System.out.println("Chưa có thẻ.");
}
}
→ Tương tự với HasCardState
, AuthenticatedState
, v.v.
✅ 20.5. Lợi ích của State Pattern
Lợi ích | Giải thích |
---|---|
Loại bỏ if/else phức tạp | Mỗi trạng thái tự định nghĩa hành vi |
Dễ thêm trạng thái mới | Thêm class, không chạm logic cũ |
Tách biệt logic theo vai trò | Mỗi class xử lý 1 trạng thái duy nhất |
Runtime thay đổi hành vi linh hoạt | Chỉ cần gọi context.setState() |
⚠️ 20.6. Nhược điểm & lưu ý
Hạn chế | Gợi ý |
---|---|
Nhiều class nhỏ lẻ | Gom các state đơn giản thành enum-strategy nếu được |
Giao tiếp giữa state và context phức tạp | Dùng truyền Context vào constructor |
Trạng thái thay đổi không rõ ràng | Nên log lại mỗi lần đổi trạng thái |
📚 20.7. Tình huống sử dụng thực tế
Tình huống | State pattern giúp gì? |
---|---|
Game: nhân vật ở trạng thái khác nhau | Attack(), jump() tùy theo trạng thái |
Đơn hàng: pending, shipped, delivered | Mỗi trạng thái xử lý logic riêng |
Cổng thanh toán: init, verified, failed | Mỗi trạng thái kiểm soát flow khác nhau |
Giao diện đa bước (form wizard) | Từng bước là 1 state |
🔁 20.8. So sánh State vs Strategy
Tiêu chí | State Pattern | Strategy Pattern |
---|---|---|
Dựa trên trạng thái nội tại | ✅ Có | ❌ Không |
Thay đổi hành vi runtime | ✅ | ✅ |
Dùng cho điều kiện nội bộ | ✅ | ❌ (thường do người dùng chọn) |
Giao diện người dùng thích hợp | Game, máy trạng thái | Bộ lọc, thuật toán lựa chọn |
💬 Ghi nhớ chương này:
- State Pattern giúp object:
- Tự điều chỉnh hành vi mà không cần
if/else
- Chuyển đổi giữa các trạng thái một cách rõ ràng
- Tự điều chỉnh hành vi mà không cần
- Phù hợp với:
- Game
- Order flow
- ATM, UI Wizard
- Quy trình có trạng thái thay đổi tuần tự
🔄 Nếu “một object nhiều khuôn mặt” tùy thời điểm – hãy dùng State Pattern.
Chain of Responsibility Pattern
Xử lý theo chuỗi, đến khi có người nhận.
🎯 21.1. Vấn đề: nhiều handler, nhưng không biết ai xử lý
Bạn có hệ thống:
- Middleware kiểm tra request: xác thực, phân quyền, ghi log…
- Bộ phận tiếp nhận khiếu nại: nhân viên thường → trưởng phòng → giám đốc
- Trình duyệt sự kiện: ai đó nhận, nếu không thì chuyển tiếp
Nếu dùng if...else
:
javaCopyEditif (isAdmin(user)) { ... }
else if (isManager(user)) { ... }
else if (isSupport(user)) { ... }
→ ❌ Cứng nhắc
→ ❌ Không mở rộng được
→ ❌ Không thay đổi thứ tự xử lý dễ dàng
✅ 21.2. Mục tiêu của Chain of Responsibility Pattern
Cho phép bạn truyền yêu cầu qua một chuỗi các handler. Mỗi handler quyết định xử lý hoặc chuyển tiếp yêu cầu đến handler tiếp theo trong chuỗi.
🧱 21.3. Cấu trúc tổng quát
Thành phần | Vai trò |
---|---|
Handler (interface) | Khai báo hàm setNext() và handle() |
ConcreteHandler | Xử lý yêu cầu nếu phù hợp, nếu không → chuyển tiếp |
Client | Gửi yêu cầu vào đầu chuỗi |
✍️ 21.4. Ví dụ Java – Xử lý đơn nghỉ phép
Giao diện handler:
javaCopyEditinterface LeaveApprover {
void setNext(LeaveApprover approver);
void processLeave(int days);
}
Các handler cụ thể:
javaCopyEditclass Manager implements LeaveApprover {
private LeaveApprover next;
public void setNext(LeaveApprover approver) {
this.next = approver;
}
public void processLeave(int days) {
if (days <= 2) {
System.out.println("Manager duyệt nghỉ " + days + " ngày.");
} else if (next != null) {
next.processLeave(days);
}
}
}
class Director implements LeaveApprover {
private LeaveApprover next;
public void setNext(LeaveApprover approver) {
this.next = approver;
}
public void processLeave(int days) {
if (days <= 5) {
System.out.println("Director duyệt nghỉ " + days + " ngày.");
} else if (next != null) {
next.processLeave(days);
} else {
System.out.println("Không ai có quyền duyệt nghỉ quá 5 ngày.");
}
}
}
Sử dụng:
javaCopyEditManager manager = new Manager();
Director director = new Director();
manager.setNext(director);
manager.processLeave(1); // Manager xử lý
manager.processLeave(4); // Director xử lý
manager.processLeave(10); // Không xử lý được
✅ 21.5. Lợi ích của Chain of Responsibility
Lợi ích | Giải thích |
---|---|
Tăng tính mở rộng | Dễ thêm/xoá handler |
Giảm phụ thuộc giữa các thành phần | Mỗi handler không biết gì về trước đó |
Linh hoạt thay đổi thứ tự xử lý | Dễ sắp xếp lại chuỗi |
Phù hợp cho pipeline xử lý | Logging, validation, security middleware… |
⚠️ 21.6. Cảnh báo và nhược điểm
Hạn chế | Gợi ý |
---|---|
Yêu cầu không được xử lý | Cần handler cuối cùng hiển thị fallback |
Debug khó nếu chuỗi dài | Cần log từng bước trong chuỗi |
Gắn chặt nếu setNext thủ công | Dùng builder hoặc cấu hình động nếu chuỗi dài |
📚 21.7. Ứng dụng thực tế
Tình huống | Áp dụng |
---|---|
Middleware HTTP (auth, CORS, log) | Mỗi lớp là 1 handler |
Giao diện sự kiện UI | Event handler truyền dần |
Quy trình xét duyệt | Mỗi cấp quản lý là 1 handler |
Chatbot trả lời theo từ khoá | Handler theo priority |
Validate form theo nhiều bước | Chuỗi kiểm tra logic, ràng buộc |
🔁 21.8. So sánh với các mẫu khác
So sánh | Chain of Responsibility |
---|---|
Xử lý 1 trong nhiều đối tượng | ✅ |
Cho phép mở rộng handler | ✅ |
Dựa theo chuỗi tuyến tính | ✅ |
So với Strategy | Strategy chọn 1, Chain kiểm từng cái |
💬 Ghi nhớ chương này:
- Chain of Responsibility phù hợp khi:
- Không biết ai sẽ xử lý yêu cầu
- Muốn chia nhỏ trách nhiệm thành từng phần
- Cần pipeline, middleware, hệ thống approval
- Giúp tăng khả năng mở rộng và tách biệt logic xử lý
🔗 Bạn không cần biết ai sẽ giúp – cứ “đưa yêu cầu lên chuỗi”.
Memento Pattern
Khi bạn cần Undo, Save, và Rollback
🎯 22.1. Vấn đề: làm sao để “quay lại như chưa từng xảy ra”
Giả sử bạn xây dựng:
- Ứng dụng soạn thảo văn bản
- Trò chơi (game) cần lưu lại checkpoint
- Hệ thống cấu hình cần lưu/khôi phục trạng thái trước đó
Bạn không thể expose toàn bộ nội dung bên trong object ra ngoài, nhưng vẫn cần:
✅ Lưu lại
✅ Khôi phục đúng trạng thái trước
✅ Không phá vỡ đóng gói (encapsulation)
✅ 22.2. Mục tiêu của Memento Pattern
Cho phép bạn lưu trạng thái nội tại của một object mà không để lộ chi tiết cấu trúc bên trong, và có thể khôi phục lại trạng thái đó sau này.
🧠 22.3. Cách hiểu đơn giản: “Bấm Ctrl + Z”
- Bạn viết 1 dòng → hệ thống tự lưu trạng thái trước đó
- Khi bấm “Undo”, trạng thái đó được phục hồi
- Nhưng bên ngoài không thấy rõ hệ thống lưu như thế nào
→ Đó là Memento
🧱 22.4. Cấu trúc tổng quát
Thành phần | Vai trò |
---|---|
Originator | Đối tượng chứa trạng thái cần lưu |
Memento | Gói dữ liệu trạng thái, không cho chỉnh sửa |
Caretaker | Lưu trữ và phục hồi Memento |
✍️ 22.5. Ví dụ Java – Trình soạn thảo đơn giản
Memento:
javaCopyEditclass EditorMemento {
private final String content;
public EditorMemento(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
Originator:
javaCopyEditclass Editor {
private String content;
public void write(String text) {
content += text;
}
public EditorMemento save() {
return new EditorMemento(content);
}
public void restore(EditorMemento memento) {
this.content = memento.getContent();
}
public String getContent() {
return content;
}
}
Caretaker:
javaCopyEditclass History {
private Stack<EditorMemento> history = new Stack<>();
public void save(Editor editor) {
history.push(editor.save());
}
public void undo(Editor editor) {
if (!history.empty()) {
editor.restore(history.pop());
}
}
}
Sử dụng:
javaCopyEditEditor editor = new Editor();
History history = new History();
editor.write("Hello ");
history.save(editor);
editor.write("World!");
System.out.println(editor.getContent()); // Hello World!
history.undo(editor);
System.out.println(editor.getContent()); // Hello
✅ 22.6. Lợi ích của Memento Pattern
Lợi ích | Giải thích |
---|---|
Hoàn tác (Undo) dễ dàng | Chỉ cần lưu & restore Memento |
Giữ nguyên đóng gói (encapsulation) | Không để lộ nội dung class ra ngoài |
Dễ mở rộng cho phiên bản, checkpoint | Dùng trong game, trình soạn thảo, cấu hình |
⚠️ 22.7. Hạn chế và lưu ý
Hạn chế | Giải pháp |
---|---|
Memento có thể nặng nếu object lớn | Nén/ghi log vi sai (diff) |
Lưu trữ nhiều version tiêu tốn RAM | Dùng giới hạn số phiên bản |
Phải cẩn thận khi restore sai thời điểm | Quản lý bằng timestamp hoặc id |
📚 22.8. Ứng dụng thực tế
Tình huống | Ứng dụng |
---|---|
Soạn thảo văn bản (MS Word, Google Docs) | Undo/Redo |
Game – checkpoint, load save | Lưu trạng thái level, HP, XP… |
Giao diện nhập liệu nhiều bước | Quay lại bước trước |
Hệ thống cấu hình nâng cao | Rollback config |
Biểu đồ thay đổi dữ liệu theo thời gian | Lưu từng snapshot bằng Memento |
🔁 22.9. So sánh với các mẫu khác
Mẫu | Khác biệt chính |
---|---|
Command | Biểu diễn hành động |
Memento | Biểu diễn trạng thái |
State | Thay đổi hành vi theo trạng thái nội tại |
Memento + Command | = Undo có hành động + dữ liệu |
💬 Ghi nhớ chương này:
- Memento = “đóng gói trạng thái, khôi phục linh hoạt”
- Dùng khi:
- Cần Undo
- Cần lưu lại trạng thái hệ thống
- Không muốn tiết lộ nội bộ object
- Phù hợp với:
- Text editor
- Game checkpoint
- Cấu hình đa phiên bản
🧳 Khi bạn cần “quay ngược thời gian” cho object – hãy dùng Memento.
Visitor Pattern
Khi bạn cần thêm hành vi mà không được sửa class.
🎯 23.1. Vấn đề: Không thể hoặc không nên sửa class gốc
Bạn có hệ thống:
- Danh sách
Employee
,Manager
,CEO
- Bạn muốn in lương, tính thuế, xuất PDF…
→ Các hành vi đó không nên nhét hết vào class Employee
Nhưng nếu bạn không được sửa class (class từ thư viện, hệ thống cũ…),
hoặc không muốn phá vỡ nguyên tắc Single Responsibility?
→ Visitor Pattern là giải pháp.
✅ 23.2. Mục tiêu của Visitor Pattern
Cho phép bạn tách riêng các hành vi khỏi các đối tượng cần xử lý
→ bằng cách truyền “visitor” vào object để thực hiện hành vi.
- Giữ object gọn nhẹ
- Không phá vỡ đóng gói
- Dễ thêm logic xử lý mới mà không sửa object
🧱 23.3. Cấu trúc tổng quát
Thành phần | Vai trò |
---|---|
Element (interface) | Giao diện object có thể “được thăm” |
ConcreteElement | Lớp thật – chỉ cần triển khai accept(visitor) |
Visitor (interface) | Giao diện của các hành vi |
ConcreteVisitor | Mỗi visitor là một loại xử lý |
✍️ 23.4. Ví dụ Java – Nhân sự và tính thuế
Interface Employee
:
javaCopyEditinterface Employee {
void accept(Visitor visitor);
}
Các class:
javaCopyEditclass Developer implements Employee {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public double getSalary() { return 1000; }
}
class Manager implements Employee {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public double getSalary() { return 2000; }
}
Visitor:
javaCopyEditinterface Visitor {
void visit(Developer d);
void visit(Manager m);
}
Tính thuế:
javaCopyEditclass TaxCalculator implements Visitor {
public void visit(Developer d) {
System.out.println("Thuế Dev: " + d.getSalary() * 0.1);
}
public void visit(Manager m) {
System.out.println("Thuế Manager: " + m.getSalary() * 0.2);
}
}
Sử dụng:
javaCopyEditList<Employee> staff = List.of(new Developer(), new Manager());
Visitor tax = new TaxCalculator();
for (Employee e : staff) {
e.accept(tax);
}
✅ 23.5. Lợi ích của Visitor Pattern
Lợi ích | Giải thích |
---|---|
Thêm hành vi mới mà không sửa class | Chỉ cần tạo visitor mới |
Áp dụng đa hình cho hành vi | Phân loại xử lý theo từng object cụ thể |
Duy trì nguyên tắc Single Responsibility | Object chỉ giữ dữ liệu, không xử lý |
⚠️ 23.6. Nhược điểm và lưu ý
Hạn chế | Lưu ý |
---|---|
Mỗi Visitor phải biết mọi loại class | Mỗi visit(X) phải viết riêng |
Dễ gây rối nếu object thay đổi liên tục | Phù hợp với hệ thống ổn định |
Không phù hợp nếu object hierarchy thường xuyên đổi | Visitor không linh hoạt như Strategy |
📚 23.7. Ứng dụng thực tế
Tình huống | Áp dụng Visitor |
---|---|
In thông tin nhiều kiểu đối tượng | Visitor = Printer |
Tính thuế, báo cáo, thống kê | Visitor = TaxCalculator, ReportGenerator |
Duyệt cây AST (compiler) | Visitor = Evaluator, Translator |
Quét các phần tử trong DOM/XML | Visitor = NodeScanner |
🔁 23.8. So sánh Visitor vs Strategy vs Command
Mẫu | Dùng khi |
---|---|
Strategy | Chọn thuật toán khác nhau cho 1 object |
Command | Biểu diễn hành động có thể delay, queue |
Visitor | Thêm hành vi mới cho nhiều object khác nhau mà không sửa object |
💬 Ghi nhớ chương này:
- Visitor = đưa hành vi vào class từ bên ngoài
- Phù hợp khi:
- Object không nên hoặc không được sửa
- Muốn thêm nhiều logic xử lý riêng cho object
- Hệ thống ổn định về cấu trúc class
🧳 “Đừng nhét mọi thứ vào object – hãy để visitor đến làm thay.”
Mediator Pattern
Tập trung điều phối thay vì quan hệ chằng chịt
🎯 24.1. Vấn đề: Quá nhiều đối tượng gọi lẫn nhau gây rối
Giả sử bạn có form gồm nhiều component:
TextBox
,Checkbox
,Button
,Label
- Khi nhập Text → bật nút Submit
- Khi click Checkbox → đổi nội dung Label
- Khi click Submit → lock hết các ô nhập
→ Nếu các component gọi trực tiếp lẫn nhau:
javaCopyEdittextBox.onChange() → button.enable()
checkbox.onClick() → label.setText()
...
→ ❌ Gây phụ thuộc chéo,
→ ❌ Khó test, khó mở rộng, dễ lỗi dây chuyền
✅ 24.2. Mục tiêu của Mediator Pattern
Giảm mối quan hệ trực tiếp giữa các đối tượng bằng cách đưa việc giao tiếp qua một “điều phối viên” trung tâm (mediator).
- Các object không gọi nhau
- Chỉ giao tiếp với Mediator
- Mediator quyết định: ai sẽ phản ứng, phản ứng thế nào
🧱 24.3. Cấu trúc tổng quát
Thành phần | Vai trò |
---|---|
Mediator (interface) | Định nghĩa phương thức giao tiếp |
ConcreteMediator | Điều phối giao tiếp giữa các component |
Colleague (component) | Các object liên quan, giao tiếp qua Mediator |
✍️ 24.4. Ví dụ Java – Giao diện đơn giản
Mediator:
javaCopyEditinterface UIMediator {
void notify(Component sender, String event);
}
Component cha:
javaCopyEditabstract class Component {
protected UIMediator mediator;
public Component(UIMediator mediator) {
this.mediator = mediator;
}
}
Component cụ thể:
javaCopyEditclass TextBox extends Component {
public void inputText(String text) {
System.out.println("User nhập: " + text);
mediator.notify(this, "textEntered");
}
}
class Button extends Component {
public void setEnabled(boolean enabled) {
System.out.println("Button " + (enabled ? "bật" : "tắt"));
}
}
ConcreteMediator:
javaCopyEditclass FormMediator implements UIMediator {
private TextBox textBox;
private Button button;
public void setComponents(TextBox t, Button b) {
this.textBox = t;
this.button = b;
}
public void notify(Component sender, String event) {
if (sender == textBox && event.equals("textEntered")) {
button.setEnabled(true);
}
}
}
Sử dụng:
javaCopyEditFormMediator mediator = new FormMediator();
TextBox text = new TextBox(mediator);
Button btn = new Button(mediator);
mediator.setComponents(text, btn);
text.inputText("Hello"); // Button bật
✅ 24.5. Lợi ích của Mediator Pattern
Lợi ích | Giải thích |
---|---|
Giảm số mối quan hệ phụ thuộc | Object không biết về nhau |
Dễ bảo trì, dễ mở rộng | Thêm logic mới chỉ cần thay đổi Mediator |
Tăng tính module hóa | Component có thể tái sử dụng |
Tăng tính kiểm soát luồng giao tiếp | Dễ theo dõi sự kiện xảy ra khi nào |
⚠️ 24.6. Hạn chế và lưu ý
Hạn chế | Gợi ý |
---|---|
Mediator có thể bị “phình to” | Nếu xử lý quá nhiều logic – chia thành nhiều mediator nhỏ |
Phức tạp nếu lạm dụng | Dùng khi số lượng object lớn và quan hệ chồng chéo |
📚 24.7. Ứng dụng thực tế
Tình huống | Áp dụng Mediator |
---|---|
Giao diện GUI, Form, Wizard | Các control giao tiếp qua Mediator |
Game: NPC giao tiếp, AI ra quyết định | Mediator tổ chức phản ứng của nhóm |
Hệ thống event-driven (event bus) | Mediator trung gian phát sự kiện |
Chatroom: người dùng không gửi trực tiếp | Server chat = Mediator |
🔁 24.8. So sánh Mediator vs Observer
Tiêu chí | Mediator | Observer |
---|---|---|
Chủ động điều phối | ✅ Có | ❌ Không (thụ động lắng nghe) |
Mối quan hệ 2 chiều | ✅ Có | Chủ yếu 1 chiều từ Subject → Observer |
Quản lý nhiều logic phức hợp | ✅ | ❌ Observer đơn giản hơn |
💬 Ghi nhớ chương này:
- Mediator là “bộ điều khiển trung tâm” giúp các thành phần không cần biết nhau vẫn phối hợp được
- Phù hợp khi:
- Số lượng class nhiều
- Quan hệ chằng chịt
- Hệ thống cần tách UI và xử lý logic rõ ràng
🧩 Khi hệ thống trở thành “mạng nhện” của lời gọi phương thức – Mediator là người gỡ rối.
Interpreter Pattern
Xây dựng bộ diễn giải cho ngôn ngữ đơn giản
🎯 25.1. Vấn đề: Cần phân tích và thực thi “ngôn ngữ riêng”
Bạn có yêu cầu:
- Cho phép người dùng viết câu lệnh như:
A AND (B OR C)
- Viết công thức rút gọn:
x + y * z
- Duyệt cây biểu thức (AST) hoặc DSL nội bộ (MiniLang)
→ Bạn cần một cách để “diễn giải” những câu lệnh đó, và xử lý như logic thật sự
✅ 25.2. Mục tiêu của Interpreter Pattern
Cho phép bạn định nghĩa cú pháp và cách diễn giải một ngôn ngữ đơn giản, thường dùng cho:
- Máy tính biểu thức
- Rule engine
- Command mini language
- Cây toán học / logic
🧱 25.3. Cấu trúc tổng quát
Thành phần | Vai trò |
---|---|
Expression (interface) | Khai báo hàm interpret(Context) |
TerminalExpression | Thành phần cơ bản (số, biến, true/false…) |
NonTerminalExpression | Thành phần kết hợp (AND, OR, Add, Multiply…) |
Context | Dữ liệu/biến cần cho việc interpret |
✍️ 25.4. Ví dụ Java – Biểu thức logic đơn giản
Giả sử người dùng nhập: A AND (B OR C)
Ta biểu diễn:
Interface:
javaCopyEditinterface Expression {
boolean interpret(Map<String, Boolean> context);
}
Terminal:
javaCopyEditclass Variable implements Expression {
private String name;
public Variable(String name) { this.name = name; }
public boolean interpret(Map<String, Boolean> context) {
return context.get(name);
}
}
Non-terminal:
javaCopyEditclass Or implements Expression {
private Expression left, right;
public Or(Expression l, Expression r) {
left = l; right = r;
}
public boolean interpret(Map<String, Boolean> context) {
return left.interpret(context) || right.interpret(context);
}
}
class And implements Expression {
private Expression left, right;
public And(Expression l, Expression r) {
left = l; right = r;
}
public boolean interpret(Map<String, Boolean> context) {
return left.interpret(context) && right.interpret(context);
}
}
Sử dụng:
javaCopyEditExpression A = new Variable("A");
Expression B = new Variable("B");
Expression C = new Variable("C");
Expression expression = new And(A, new Or(B, C));
Map<String, Boolean> context = Map.of(
"A", true,
"B", false,
"C", true
);
System.out.println(expression.interpret(context)); // true
✅ 25.5. Lợi ích của Interpreter Pattern
Lợi ích | Giải thích |
---|---|
Xây ngôn ngữ mini dễ dàng | Tách biệt syntax và logic |
Tạo cấu trúc cây logic rõ ràng | Rất giống AST trong compiler |
Dễ mở rộng thêm toán tử mới | Chỉ cần thêm class mới implement Expression |
⚠️ 25.6. Nhược điểm và cảnh báo
Hạn chế | Giải pháp |
---|---|
Không phù hợp với ngôn ngữ phức tạp | Dùng parser thật (ANTLR, yacc…) |
Cây lồng nhau nhiều → khó debug | Dùng biểu diễn hậu tố (postfix), caching |
Thiếu tối ưu hiệu suất | Cần compile hoặc optimize nếu dùng thực tế |
📚 25.7. Ứng dụng thực tế
Tình huống | Áp dụng |
---|---|
Hệ thống filter sản phẩm (A AND B ) | Diễn giải logic |
Rule Engine nhỏ | Xử lý điều kiện động |
Máy tính mini / công thức toán học | Dùng cho học sinh, khoa học cơ bản |
Trình thông dịch biểu thức cấu hình | Ví dụ: price > 100 and category = 'A' |
🔁 25.8. So sánh với các mẫu khác
Mẫu | So sánh |
---|---|
Interpreter | Tự xây cú pháp và cách hiểu cho DSL |
Visitor | Thêm hành vi cho các node của cây |
Strategy | Chọn thuật toán đã có |
Command | Biểu diễn lệnh đơn, không có ngữ pháp |
💬 Ghi nhớ chương này:
- Interpreter giúp xử lý logic “dạng ngôn ngữ” bên trong ứng dụng
- Phù hợp khi:
- Có ngôn ngữ miền hẹp (mini DSL)
- Cần tách dữ liệu – cú pháp – xử lý
- Rất mạnh trong: parser đơn giản, rule engine, DSL nội bộ
🧠 Khi object của bạn cần hiểu và thực hiện “ngôn ngữ riêng” – hãy nghĩ đến Interpreter.