Design Pattern từ đơn giản đến phức tạp(P2 Factory method pattern)
Câu chuyện về lịch sử, khái niệm Design Pattern đã được mở màn ở Chapter 1. Nhắc lại một chút là Design Pattern kinh điển được chia thành 3 nhóm. Ở Chapter này chúng ta sẽ nói chuyện về nhóm khởi tạo(Creational pattern).
Nhóm khởi tạo có 5 mẫu chính bao gồm Abstract factory pattern, Builder pattern, Factory method pattern, Prototype pattern, Singleton pattern. Trong bài này chúng ta sẽ thảo luận về mẫu kinh điển đầu tiên Factory method pattern.
- Factory method pattern là mẫu kinh điển cung cấp Interface để giúp tạo đối tượng trong Class cha(Super Class) nhưng cũng cho phép các class con(Subclass) có thể thay thế loại đối tượng sẽ được tạo.
Hơi khó hiểu đối với anh em mới lập trình viên nếu nghe thấy định nghĩa là như vậy. Không sao, chúng ta sẽ đi sâu vào phân tích về Pattern này để hiểu vấn đề:
- Vấn đề: Chúng ta có một tình huống là như sau. Giả sử ta đi xây dựng một ứng dụng vận tải. Đầu tiên ứng dụng chỉ phục vụ việc vận chuyển bằng đường bộ(Truck class). Hình dung khi doanh nghiệp phát triển, yêu cầu phát sinh về vận chuyển đường thuỷ(Sea class) là điều không tránh khỏi.
Thông thường đối với các lập trình viên sẽ gắn chặt LogisticApp với Truck class(Vận chuyển đường bộ bằng ô tô). Thậm chí mình đã từng review khá nhiều source code điều đơn giản mình thấy là các bạn thường đưa toàn bộ logic xử lí vào class Truck. Điều này đơn giản dẫn đến việc chuyển đổi, thêm mới một logic trở nên rất tốn thời gian
Giải pháp: Việc đầu tiên cần suy nghĩ về mặt giải pháp ở đây không phải là dùng Design Pattern nào. Mà việc đầu tiên chúng ta cần nghĩ đến một thiết kế tốt, nơi mà chúng ta dễ dàng thay đổi source code, dễ dàng sửa chữa thêm mới các logic. Nơi mà việc áp dụng hướng đối tượng trở lên triệt để hơn. Trở lại bài toán trên, hãy hình dung nếu thiết kế đủ tốt ta có thể design như hình dưới đây:
Quan sát hình trên chúng ta thấy có thể chia việc cài đặt làm 2 thành phần. 1 là Logistics class(Supper class or Interface), nơi được định nghĩa các logic(các hàm/method) ở mức Abstract và các lớp con kế thừa(Được dùng để định nghĩa các Logic cụ thể như vận chuyển bằng đường bộ hoặc đường thuỷ). Tuy nhiên hãy hình dung sâu hơn về thiết kế trên. Đặt giả thiết rằng bản chất các công việc Logistics là khá giống nhau và chỉ khác nhau về mặt phương tiện. Nếu thiết kế như ở trên nghĩa là chúng ta sẽ giúp việc code trở lên clear(Tách logic rõ ràng) nhưng lại không giúp tối ưu được code(Tính reuseable). Giữa 2 lớp con(SubClass) việc lập trình sẽ đòi hỏi thừa ra khá nhiều logic code giống nhau. Vậy thì hãy tối ưu thêm một tầng nữa chúng ta hãy hình dung như theo hình sau:
Mọi vấn đề của bài toán nằm ở lớp Transport. Nếu chúng ta Abstract được lớp Transport thì dễ dàng thấy là ứng dụng LogicticsApp chỉ quan tâm đến Transport. Các logic xử lí vận chuyển khác có thể định nghĩa ở class LogicticsApp. Việc quyết định Transport khi cài đặt là đối tượng nào hoàn toàn có thể do Subclass(Thừa kế từ LogicticsApp) quyết định
Ok nếu design như vậy ta sẽ có FactoryMethod nơi mà việc cài đặt khởi tạo đối tượng thực thi thực sự được quyết định bởi SubClass
Lưu ý: Hãy nhớ rằng việc lập trình có khá nhiều cách để cài đặt. Với bài toán trên cũng vậy, chúng ta có thể dùng nhiều cách khác nhau, nhiều Pattern khác nhau để cài đặt. Vấn đề ở đây ngoài việc chúng ta cần hiểu Design pattern có thể áp dụng ỏ những ngữ cảnh thế nào thì hơn thế nữa chúng ta nên biết nó được áp dụng trong tình huống nào
Đối với Factory Method:
- Sử dụng khi bạn chưa trước được loại hình đối tượng(Transport) và không rõ được đối tượng phát sinh cụ thể(Truck, Ship)
- Khi bạn muốn cung cấp các thư viện nơi mà bắt buộc Client phải sử dụng logic của bạn(LogisticApp) và cài đặt các đối tượng phát sinh của riêng họ
- Khi bạn muốn tối ưu hoá lại quy trình(Đảm bảo các phương tiện vận chuyển — Transport phải thực hiện quy trình Logistics) và đảm bảo tính reuseable của code ở mức cao
Về cách cài đặt:
Link Tham khảo: https://refactoring.guru/design-patterns/factory-method