Nội dung
Mục tiêu
Abstract Factory là một mẫu thiết kế khởi tạo cho phép bạn tạo ra các họ của các đối tượng liên quan mà không cần chỉ định các lớp cụ thể của chúng.
Đặt vấn đề
Hãy tưởng tượng rằng bạn đang tạo một ứng dụng mô phỏng cửa hàng đồ nội thất. Chương trình của bạn bao gồm các lớp sau:
- Một nhóm các sản phẩm liên quan, chẳng hạn như:
Chair
+Sofa
+CoffeeTable
. - Một số biến thể của nhóm này. Ví dụ, các sản phẩm
Chair
+Sofa
+CoffeeTable
có sẵn với các biến thể sau:Modern
,Victorian
,ArtDeco
.
Bạn cần một cách để tạo các đồ nội thất riêng lẻ sao cho chúng phù hợp với các đồ vật khác trong cùng một nhóm. Khách hàng khá tức giận khi nhận được đồ nội thất không phù hợp.
Ngoài ra, bạn không muốn thay đổi chương trình hiện có khi thêm vào sản phẩm hoặc nhóm sản phẩm mới. Các nhà cung cấp đồ nội thất cập nhật danh mục của họ rất thường xuyên và bạn sẽ không muốn thay đổi lõi chương trình mỗi lần nó xảy ra.
Giải pháp
Điều đầu tiên mà mẫu Abstract Factory gợi ý là khai báo rõ ràng các giao diện cho từng sản phẩm riêng biệt trong nhóm sản phẩm (ví dụ: ghế, sofa hoặc bàn cà phê). Sau đó, bạn có thể để tất cả các biến thể của sản phẩm tuân theo các giao diện đó. Ví dụ: tất cả các biến thể của Chair
đều có thể triển khai giao diện Chair
; tất cả các biến thể CoffeeTable
đều có thể triển khai giao diện CoffeeTable
, v.v.
Bước tiếp theo là khai báo Abstract Factory — một giao diện với danh sách các phương thức khởi tạo cho tất cả các sản phẩm thuộc nhóm sản phẩm (createChair
, createSofa
và createCoffeeTable
). Các phương thức này phải trả về các loại sản phẩm trừu tượng được đại diện bởi các giao diện mà chúng tôi đã trích xuất trước đây: Chair
, Sofa
, CoffeeTable
, v.v.
Bây giờ, với các biến thể của sản phẩm thì sao? Đối với từng biến thể của một nhóm sản phẩm, chúng ta tạo một lớp factory riêng dựa trên giao diện AbstractFactory
. Một factory là một lớp trả lại các sản phẩm của một loại cụ thể. Ví dụ: ModernFurnitureFactory
chỉ có thể tạo các đối tượng ModernChair
, ModernSofa
và ModernCoffeeTable
.
Mã Client phải hoạt động với cả factory và sản phẩm thông qua các giao diện trừu tượng tương ứng của chúng. Điều này cho phép bạn thay đổi loại factory mà bạn truyền cho client, cũng như biến thể sản phẩm mà client nhận được, mà không phá vỡ mã client thực tế.
Giả sử khách hàng (client) muốn một nhà máy (factory) sản xuất một chiếc ghế. Khách hàng không cần phải biết về loại ghế của nhà máy, cũng như không quan trọng loại ghế mà họ nhận được. Cho dù đó là ghế kiểu Modern hay Victorian, khách hàng phải xử lý tất cả các ghế theo cùng một cách, sử dụng thông qua giao diện Chair
. Với cách tiếp cận này, điều duy nhất mà khách hàng biết về chiếc ghế là nó thực hiện phương thức sitOn
theo một cách nào đó. Ngoài ra, bất kỳ biến thể nào của ghế được trả lại, nó sẽ luôn khớp với loại ghế sofa hoặc bàn cà phê được sản xuất bởi cùng một đối tượng factory.
Còn một điều nữa cần làm rõ, nếu khách hàng chỉ tiếp xúc với các giao diện trừu tượng, thì điều gì tạo ra các đối tượng factory thực sự? Thông thường, ứng dụng tạo một đối tượng factory cụ thể ở giai đoạn khởi tạo. Ngay trước đó, ứng dụng phải chọn loại xuất xưởng tùy thuộc vào cài đặt hoặc môi trường.
Cấu trúc
- Abstract Product (ProductA, ProductB) khai báo giao diện cho một tập hợp các sản phẩm riêng biệt nhưng có liên quan tạo nên một nhóm sản phẩm.
- Concrete Product là các sản phẩm cụ thể triển khai theo các Abstract Product, được nhóm theo các biến thể. Mỗi sản phẩm trừu tượng (chair/sofa) phải được thực hiện trong tất cả các biến thể nhất định (Victorian/Modern).
- Giao diện Abstract Factory khai báo một tập hợp các phương thức khởi tạo cho từng sản phẩm trừu tượng.
- Concrete Factory thực hiện các phương thức khởi tạo của giao diện Abstract Factory. Mỗi Concrete Factory tương ứng với một biến thể cụ thể của sản phẩm và chỉ tạo ra những biến thể sản phẩm đó.
- Mặc dù các Concrete Factory tạo ra các sản phẩm cụ thể, nhưng chữ ký của các phương thức khởi tạo của chúng phải trả về các sản phẩm trừu tượng (abstract product) tương ứng. Bằng cách này, client sử dụng factory sẽ không cần liên kết với biến thể cụ thể của sản phẩm mà nó nhận được từ factory. Client có thể làm việc với bất kỳ biến thể nhà máy / sản phẩm cụ thể nào, miễn là nó giao tiếp với các đối tượng thông qua các giao diện trừu tượng.
Khả năng áp dụng
Sử dụng Abstract Factory khi chương trình của bạn cần hoạt động với nhiều nhóm sản phẩm liên quan khác nhau, nhưng bạn không muốn nó phụ thuộc vào các lớp cụ thể của các sản phẩm đó — chúng có thể chưa được biết trước hoặc bạn chỉ muốn cho phép khả năng mở rộng trong tương lai.
Ưu và nhược điểm
😄😄😄
Bạn có thể chắc chắn rằng các sản phẩm bạn nhận được từ factory tương thích với nhau.
Bạn tránh việc liên kết chặt chẽ giữa sản phẩm cụ thể và client.
Nguyên tắc Đơn trách nhiệm. Bạn có thể trích xuất mã khởi tạo sản phẩm vào một nơi, cập nhật mã dễ dàng hơn.
Nguyên tắc Mở/Đóng. Bạn có thể giới thiệu các biến thể mới của sản phẩm mà không cần phá vỡ mã client hiện có.
🙁🙁🙁
Chương trình có thể trở nên phức tạp hơn mức bình thường, vì rất nhiều giao diện và lớp mới được giới thiệu cùng với mẫu.
Mối quan hệ với các mẫu khác
- Nhiều thiết kế bắt đầu bằng cách sử dụng Factory Method (ít phức tạp hơn và có thể tùy chỉnh nhiều hơn thông qua các lớp con) và phát triển theo hướng Abstract Factory, Prototype hoặc Builder (linh hoạt hơn nhưng phức tạp hơn).
- Builder tập trung vào việc xây dựng các đối tượng phức tạp theo từng bước. Abstract Factory chuyên tạo nhóm các đối tượng liên quan. Abstract Factory trả về đối tượng ngay lập tức, trong khi Builder cho phép bạn chạy một số bước xây dựng bổ sung trước khi trả về đối tượng.
- Các lớp Abstract Factory thường dựa trên một tập hợp các phương thức Factory, nhưng bạn cũng có thể sử dụng Prototype để soạn các phương thức trên các lớp này.
- Abstract Factory có thể phục vụ như một giải pháp thay thế cho Facade khi bạn chỉ muốn ẩn cách các đối tượng hệ thống con được tạo ra khỏi mã client.
- Bạn có thể sử dụng Abstract Factory cùng với Bridge. Việc ghép cặp này rất hữu ích khi một số đối tượng trừu tượng được định nghĩa bởi Bridge chỉ có thể hoạt động với các hiện thực cụ thể. Trong trường hợp này, Abstract Factory có thể đóng gói các quan hệ này và ẩn sự phức tạp khỏi mã client.
- Abstract Factory, Builder và Prototype đều có thể triển khai dưới dạng Singleton.
Mã tham khảo
C++ Design patterns: Abstract Factory
Để lại một bình luận