Giới thiệu Design Patterns
Sau bài này, bạn sẽ:
- Hiểu Design Patterns là gì và tại sao chúng quan trọng
- Nắm được 3 nhóm patterns: Creational, Structural, Behavioral
- Hiểu nguyên tắc SOLID và mối quan hệ với patterns
- Biết khi nào nên và không nên áp dụng design patterns
- Có cái nhìn tổng quan để chọn pattern phù hợp cho từng vấn đề
Design Pattern là gì?
Design Pattern (mẫu thiết kế) là các giải pháp chung, có thể tái sử dụng cho các vấn đề thường gặp trong thiết kế phần mềm. Đây không phải là code hoàn chỉnh có thể copy-paste, mà là template (khuôn mẫu) để giải quyết một vấn đề có thể áp dụng trong nhiều tình huống khác nhau.
- Tái sử dụng: Giải pháp đã được kiểm chứng, không phải "phát minh lại bánh xe"
- Giao tiếp: Tạo ngôn ngữ chung giữa các developer ("Dùng Singleton cho class này")
- Tối ưu hóa: Cải thiện cấu trúc code, dễ bảo trì và mở rộng
- Best Practices: Học từ kinh nghiệm của các chuyên gia
Ví dụ minh họa
Giả sử bạn cần đảm bảo một class chỉ có một instance duy nhất trong toàn bộ ứng dụng (ví dụ: class quản lý cấu hình). Thay vì tự nghĩ ra giải pháp, bạn có thể áp dụng Singleton Pattern - một giải pháp đã được chuẩn hóa và kiểm chứng.
Lịch sử: Gang of Four (GoF)
Design Patterns trở nên phổ biến nhờ cuốn sách "Design Patterns: Elements of Reusable Object-Oriented Software" xuất bản năm 1994 bởi 4 tác giả:
- Erich Gamma
- Richard Helm
- Ralph Johnson
- John Vlissides
Họ được gọi là Gang of Four (GoF). Cuốn sách này giới thiệu 23 Design Patterns cơ bản, được chia thành 3 nhóm.
Ba nhóm Design Patterns
1. Creational Patterns (Khởi tạo)
Liên quan đến cách tạo object. Thay vì dùng new trực tiếp, các patterns này cung cấp cơ chế tạo object linh hoạt hơn.
Mục đích: Che giấu logic khởi tạo phức tạp, tăng tính linh hoạt.
2. Structural Patterns (Cấu trúc)
Liên quan đến cách tổ chức class và object để tạo thành cấu trúc lớn hơn.
Mục đích: Đơn giản hóa mối quan hệ giữa các entities, dễ mở rộng.
3. Behavioral Patterns (Hành vi)
Liên quan đến giao tiếp giữa các object và cách phân chia trách nhiệm.
Mục đích: Quản lý thuật toán, quan hệ và trách nhiệm giữa các object.
23 GoF Design Patterns
| Pattern | Nhóm | Mô tả ngắn |
|---|---|---|
| Singleton | Creational | Đảm bảo một class chỉ có một instance duy nhất |
| Factory Method | Creational | Tạo object thông qua method thay vì constructor |
| Abstract Factory | Creational | Tạo các nhóm object liên quan mà không chỉ định class cụ thể |
| Builder | Creational | Xây dựng object phức tạp từng bước một |
| Prototype | Creational | Tạo object mới bằng cách clone object có sẵn |
| Adapter | Structural | Chuyển đổi interface này sang interface khác |
| Bridge | Structural | Tách abstraction khỏi implementation |
| Composite | Structural | Tổ chức object thành cấu trúc cây (tree) |
| Decorator | Structural | Thêm behavior mới cho object một cách động |
| Facade | Structural | Cung cấp interface đơn giản cho subsystem phức tạp |
| Flyweight | Structural | Chia sẻ object để tiết kiệm bộ nhớ |
| Proxy | Structural | Cung cấp placeholder hoặc đại diện cho object khác |
| Chain of Responsibility | Behavioral | Chuyển request qua chuỗi handler |
| Command | Behavioral | Đóng gói request thành object |
| Interpreter | Behavioral | Định nghĩa grammar cho ngôn ngữ và interpret nó |
| Iterator | Behavioral | Truy cập tuần tự các phần tử mà không lộ cấu trúc bên trong |
| Mediator | Behavioral | Định nghĩa object trung gian để giảm coupling giữa các object |
| Memento | Behavioral | Lưu và khôi phục trạng thái của object |
| Observer | Behavioral | Thông báo cho nhiều object khi có thay đổi |
| State | Behavioral | Thay đổi behavior khi internal state thay đổi |
| Strategy | Behavioral | Đóng gói các thuật toán và làm chúng có thể thay thế lẫn nhau |
| Template Method | Behavioral | Định nghĩa skeleton của thuật toán, để subclass override các bước cụ thể |
| Visitor | Behavioral | Tách thuật toán khỏi cấu trúc object |
Khi nào nên dùng Design Patterns?
Nên dùng khi:
- Vấn đề lặp đi lặp lại: Bạn gặp phải vấn đề tương tự nhiều lần
- Code phức tạp: Cần cấu trúc rõ ràng để quản lý độ phức tạp
- Dự đoán thay đổi: Biết trước một phần của hệ thống sẽ thay đổi thường xuyên
- Mở rộng: Cần thêm tính năng mà không sửa code hiện tại
- Strategy Pattern: Khi có nhiều cách thanh toán (credit card, PayPal, cash)
- Observer Pattern: Khi cần cập nhật UI khi dữ liệu thay đổi
- Factory Pattern: Khi cần tạo nhiều loại object tương tự nhưng khác implementation
KHÔNG nên dùng khi:
- Over-engineering: Áp dụng pattern khi vấn đề đơn giản
- Chưa cần thiết: "Might need it later" - vi phạm YAGNI principle
- Không hiểu rõ: Dùng pattern chỉ để "show off"
- Vấn đề nhỏ: Solution đơn giản (vài dòng code) đủ giải quyết
// KHÔNG CẦN THIẾT - code đơn giản
public interface CalculatorStrategy {
int calculate(int a, int b);
}
public class AddStrategy implements CalculatorStrategy {
public int calculate(int a, int b) { return a + b; }
}
// Chỉ cần thế này thôi!
public int add(int a, int b) {
return a + b;
}
Anti-Patterns: Những điều nên tránh
1. Golden Hammer
"Tôi có một cái búa, mọi thứ đều là cái đinh"
Áp dụng một pattern quen thuộc cho mọi vấn đề, dù nó không phù hợp.
// Không phải mọi class đều cần phải là Singleton!
public class Calculator {
private static Calculator instance;
private Calculator() {} // Tại sao lại singleton?
public static Calculator getInstance() {
if (instance == null) {
instance = new Calculator();
}
return instance;
}
public int add(int a, int b) { return a + b; }
}
2. Over-engineering
Tạo cấu trúc phức tạp không cần thiết cho vấn đề đơn giản.
// Quá phức tạp cho một task đơn giản
public interface StringProcessorFactory {
StringProcessor createProcessor();
}
public abstract class AbstractStringProcessor {
protected abstract String process(String input);
}
// Trong khi chỉ cần:
public class StringUtil {
public static String toUpperCase(String input) {
return input.toUpperCase();
}
}
3. Premature Optimization
Tối ưu hóa quá sớm, làm code phức tạp khi chưa có proof về performance issue.
4. God Object
Một class làm quá nhiều việc, vi phạm Single Responsibility Principle.
UML Class Diagram Basics
Design Patterns thường được biểu diễn bằng UML Class Diagram. Hiểu UML giúp bạn đọc và áp dụng patterns hiệu quả.
Các ký hiệu cơ bản
┌─────────────────┐
│ ClassName │ ← Class name (in đậm)
├─────────────────┤
│ - privateField │ ← Attributes
│ + publicField │ - : private
│ # protectedField│ + : public
├─────────────────┤ # : protected
│ + method() │ ← Methods
│ - privateMethod()│
└─────────────────┘
Mối quan hệ (Relationships)
| Ký hiệu | Tên | Ý nghĩa |
|---|---|---|
───────▷ | Inheritance (Kế thừa) | Class con extends class cha |
- - - -▷ | Implementation (Triển khai) | Class implements interface |
───────> | Association (Liên kết) | Class A có quan hệ với class B |
───────◇ | Aggregation (Tập hợp) | Class A chứa class B (B có thể tồn tại độc lập) |
───────◆ | Composition (Hợp thành) | Class A chứa class B (B không tồn tại nếu thiếu A) |
- - - -> | Dependency (Phụ thuộc) | Class A sử dụng class B tạm thời |
Ví dụ đọc UML Diagram
┌─────────────┐
│ <<interface>>│
│ Vehicle │
├─────────────┤
│+ start() │
│+ stop() │
└─────────────┘
△
│ implements
│
┌─────────────┐
│ Car │
├─────────────┤
│- brand │
├─────────────┤
│+ start() │
│+ stop() │
└─────────────┘
Giải thích: Class Car implements interface Vehicle, phải cung cấp implementation cho start() và stop().
Nguyên tắc khi học Design Patterns
- Hiểu vấn đề trước: Biết pattern giải quyết vấn đề gì
- Biết khi nào dùng: Nhận diện situation phù hợp
- Biết khi nào KHÔNG dùng: Tránh over-engineering
- Thực hành: Implement patterns trong project nhỏ
- Đọc code thực tế: Xem framework/library dùng patterns như thế nào
- Học 3-5 patterns phổ biến trước: Singleton, Factory, Observer, Strategy, Decorator
- Tìm ví dụ trong Java API:
java.iodùng Decorator, Collections dùng Iterator - Refactor code cũ: Áp dụng pattern vào code đã viết để cải thiện
- Code review: Nhận diện patterns trong code của người khác
Tóm tắt
| Khái niệm | Giải thích |
|---|---|
| Design Pattern | Giải pháp tái sử dụng cho vấn đề thường gặp |
| GoF | 4 tác giả cuốn sách Design Patterns (1994) |
| 23 GoF Patterns | Chia thành 3 nhóm: Creational, Structural, Behavioral |
| Creational | Cách tạo object (Singleton, Factory, Builder...) |
| Structural | Cách tổ chức class/object (Adapter, Decorator, Proxy...) |
| Behavioral | Cách giao tiếp giữa các object (Strategy, Observer, Command...) |
| Anti-pattern | Cách làm SAI: Over-engineering, Golden Hammer |
| UML Diagram | Công cụ biểu diễn patterns |
Bài tập
Bài 1: Nhận diện vấn đề
Với mỗi tình huống sau, bạn nghĩ pattern nào phù hợp?
- Cần tạo nhiều loại báo cáo (PDF, Excel, HTML) với format khác nhau
- Một button cần thực hiện các action khác nhau tùy context
- Cần log thông tin, nhưng chỉ muốn 1 Logger instance duy nhất
- Cần thêm tính năng mã hóa cho file mà không sửa class File
Bài 2: Anti-pattern
Tìm vấn đề trong đoạn code sau:
public class DataManager {
private static DataManager instance;
private DataManager() {}
public static DataManager getInstance() {
if (instance == null) {
instance = new DataManager();
}
return instance;
}
public void saveUser(User user) { /* ... */ }
public void sendEmail(String email) { /* ... */ }
public void generateReport() { /* ... */ }
public void processPayment(Payment p) { /* ... */ }
public void uploadFile(File f) { /* ... */ }
}
Gợi ý: Có 2 anti-patterns ở đây!
Trong các bài tiếp theo, chúng ta sẽ đi sâu vào từng nhóm patterns với ví dụ code cụ thể và bài tập thực hành.