Trong bài này chúng ta sẽ tìm hiểu design pattern là gì, vì sao lập trình viên nên học, các nhóm pattern chính và nguyên tắc SOLID nằm phía sau chúng. Đây là bài mở đầu chương design pattern của serie C++, 5 bài tiếp theo sẽ đi vào từng pattern cụ thể với code chạy được.
Bài 29 trong Serie Lập trình C++ từ A tới Z
Design pattern là gì?
Design pattern (mẫu thiết kế) là lời giải đã được kiểm chứng cho một vấn đề thiết kế phần mềm hay lặp lại. Nó không phải đoạn code để copy, mà là một cách tổ chức class và đối tượng mà các bạn áp dụng vào bài toán của mình.
Ví dụ đời thường: kiến trúc sư không phát minh lại cầu thang mỗi lần thiết kế nhà. Họ có sẵn những mẫu như cầu thang thẳng, cầu thang xoắn, cầu thang chữ L, mỗi mẫu hợp với một loại nhà. Design pattern là những mẫu như vậy cho phần mềm.
Khái niệm này phổ biến từ cuốn sách Design Patterns: Elements of Reusable Object-Oriented Software (1994) của 4 tác giả Gamma, Helm, Johnson, Vlissides, thường gọi là GoF (Gang of Four). Sách mô tả 23 pattern, tới nay vẫn là tài liệu tham khảo chuẩn.
Vì sao nên học design pattern?
- Không phát minh lại bánh xe: những vấn đề như “chỉ cần một đối tượng log cho cả chương trình” hay “báo cho nhiều nơi khi dữ liệu thay đổi” đã có lời giải tốt, không cần tự mày mò.
- Có ngôn ngữ chung với đồng nghiệp: nói “chỗ này dùng Observer” là cả nhóm hiểu ngay cấu trúc, không cần giải thích cả trang.
- Đọc hiểu thư viện, framework: Qt, Unreal Engine, thư viện của ESP32 dùng pattern khắp nơi. Biết pattern thì đọc code của họ nhanh hơn nhiều.
- Phỏng vấn: câu hỏi về Singleton, Factory, Observer xuất hiện trong hầu hết các buổi phỏng vấn vị trí C++.
3 nhóm design pattern
| Nhóm | Giải quyết | Pattern học trong serie |
|---|---|---|
| Creational (khởi tạo) | Tạo đối tượng thế nào cho linh hoạt | Singleton, Factory |
| Structural (cấu trúc) | Ghép các class, đối tượng thành cấu trúc lớn hơn | (Adapter, Decorator: tham khảo thêm) |
| Behavioral (hành vi) | Các đối tượng giao tiếp, chia trách nhiệm thế nào | Observer, Strategy, State |
Serie chọn 5 pattern dùng nhiều nhất trong thực tế và gắn với chương trình thư viện quen thuộc, thay vì đi đủ 23 pattern.
Nguyên tắc SOLID
Các pattern đều xoay quanh 5 nguyên tắc thiết kế hướng đối tượng, viết tắt là SOLID:
| Nguyên tắc | Ý nghĩa | Ví dụ trong serie |
|---|---|---|
| Single responsibility | Mỗi class chỉ làm một việc | Sach lưu thông tin sách, không tự in báo cáo hay ghi file |
| Open/closed | Mở để mở rộng, đóng để sửa đổi | Thêm loại tài liệu mới bằng class mới, không sửa vòng lặp cũ (bài đa hình) |
| Liskov substitution | Lớp con thay được cho lớp cha mà không sai | Ở đâu dùng được TaiLieu& thì truyền Sach vào cũng chạy đúng |
| Interface segregation | Nhiều interface nhỏ tốt hơn một interface lớn | Tách CoTheTimKiem, CoTheMuon thay vì một interface làm tất cả |
| Dependency inversion | Phụ thuộc vào interface, không vào class cụ thể | nhacTraSach nhận KenhThongBao& chứ không nhận Email (bài trừu tượng) |
Không cần học thuộc. Hãy coi chúng như câu hỏi tự kiểm tra khi thiết kế: class này có đang làm quá nhiều việc không? Thêm tính năng mới có phải sửa code cũ không?
Nếu đã đọc các bài tính đa hình và tính trừu tượng trong C++, các bạn đã dùng 2 nguyên tắc O và D mà không để ý. Phần lớn design pattern chính là cách áp dụng có hệ thống những công cụ đó: interface, hàm ảo, đa hình, thành phần.
Lộ trình 5 pattern trong serie
Mỗi bài theo một khung giống nhau: bài toán thực tế trong chương trình thư viện, code chưa dùng pattern và vấn đề của nó, code áp dụng pattern, khi nào nên và không nên dùng.
| Bài | Pattern | Bài toán trong thư viện |
|---|---|---|
| 30 | Singleton | Một đối tượng ghi nhật ký dùng chung cho cả chương trình |
| 31 | Factory | Đọc file dữ liệu, tạo đúng loại sách hay tạp chí theo từng dòng |
| 32 | Observer | Báo cho các độc giả đang chờ khi cuốn sách được trả |
| 33 | Strategy | Mỗi loại độc giả một cách tính tiền phạt, đổi được lúc chạy |
| 34 | State | Cuốn sách chuyển giữa các trạng thái có sẵn, đang mượn, quá hạn |
Bài 34 về State pattern có liên hệ trực tiếp với máy trạng thái trong lập trình nhúng, như bài STM32 nút nhấn 3 chế độ.
Khi nào không nên dùng design pattern?
Lưu ý: Pattern là công cụ, không phải mục tiêu. Lỗi hay gặp nhất của người mới học là cố nhét pattern vào mọi chỗ: tạo Factory cho class chỉ có một loại, viết Strategy cho phép tính không bao giờ thay đổi. Code dài gấp ba mà không giải quyết vấn đề gì. Chỉ dùng pattern khi thấy đúng vấn đề mà nó giải quyết.
Một dấu hiệu tốt: viết code đơn giản trước. Khi bắt đầu thấy đau, ví dụ thêm loại tài liệu mới phải sửa 5 chỗ, thì đó là lúc tìm pattern phù hợp để sửa lại thiết kế.
Bài tập
- Với mỗi nguyên tắc SOLID, tìm một ví dụ vi phạm trong các chương trình các bạn đã viết, và nêu cách sửa.
- Trong một phần mềm quen thuộc (ứng dụng nghe nhạc, game, phần mềm bán hàng), đoán xem chỗ nào có thể đang dùng Singleton, Observer và Strategy.
- Đọc lại bài tính trừu tượng: hàm
nhacTraSachvà interfaceKenhThongBaođang thể hiện nguyên tắc SOLID nào?
Kết
Design pattern là những lời giải đã được kiểm chứng cho các vấn đề thiết kế lặp đi lặp lại, chia thành 3 nhóm khởi tạo, cấu trúc và hành vi. Học pattern giúp các bạn thiết kế chương trình dễ mở rộng hơn, đọc hiểu framework nhanh hơn, và có ngôn ngữ chung với đồng nghiệp. Nhưng nhớ nguyên tắc quan trọng nhất: chỉ dùng pattern khi có đúng vấn đề nó giải quyết.
Tham khảo thêm danh sách đầy đủ các pattern tại Refactoring.Guru, trang có giải thích và ví dụ C++ cho từng pattern. Bài tiếp theo chúng ta bắt đầu với pattern đơn giản và nổi tiếng nhất: Singleton. Nếu thấy bài viết có ích, hãy chia sẻ cho bạn bè nhé.


