Creational Patterns - Abstract Factory
Khái niệm
Abstract Factory Pattern là một trong những mẫu thiết kế khởi tạo (Creational Pattern), nó cung cấp một interface để tạo ra các tập hợp đối tượng có liên quan hoặc phụ thuộc nhau mà không cần chỉ định rõ các lớp cụ thể. Nó được ví như một "nhà máy của các nhà máy", giúp ẩn đi sự phức tạp và đảm bảo tính đồng bộ của các sản phẩm được tạo ra.
Thành phần chính của Abstract Factory:
- Abstract Factory: Interface khai báo các phương thức tạo sản phẩm.
- Concrete Factory: Lớp hiện thực hóa Abstract Factory, tạo ra các biến thể sản phẩm cụ thể.
- Abstract Product/Product: Interface và lớp cụ thể của các sản phẩm được tạo ra.
Ưu điểm:
- Đảm bảo các sản phẩm được tạo ra tương thích với nhau.
- Giảm sự phụ thuộc (coupling) giữa code phía máy khách (client) và các lớp sản phẩm cụ thể.
- Tuân thủ nguyên tắc Trách nhiệm đơn nhất (Single Responsibility Principle): Bạn có thể di chuyển mã tạo sản phẩm vào một nơi cụ thể trong chương trình, giúp việc triển khai, nâng cấp dễ dàng hơn.
- Tuân thủ nguyên tắc Open/Closed Principle: Dễ dàng thêm các biến thể sản phẩm mới mà không sửa mã client.
Nhược điểm:
- Làm phức tạp hóa hệ thống do phải tạo ra nhiều interface và lớp.
Ví dụ thực tế
- Bài toán
Tiếp tục với bài toán trong bài viết về mẫu thiết kế Factory Method. Câu hỏi đặt ra bây giờ sẽ là: Nếu tôi muốn bổ sung thêm việc khuyến mại khi mà khách hàng gọi các đồ ăn và đồ uống có liên quan với nhau. Ví dụ: Tôi muốn thiết lập điều kiện rằng, nếu khách hàng gọi món cơm (đồ ăn), thì khách hàng sẽ được khuyến mại 1 chai nước khoáng chẳng hạn. Và nếu khách hàng gọi món phở, thì khách hàng sẽ được khuyến mại 1 chai nước coca cola.
Tương tự với các sản phẩm (đồ ăn) khác cũng sẽ có các khuyến mại tương ứng khác.
- Phân tích bài toán
Như vậy, ngoài việc đơn thuần là khách hàng gọi món và người phục vụ ghi lại các món mà khách hàng gọi. Bây giờ, khách hàng sẽ được biết thêm về các khuyến mại tương ứng với món được gọi.
Tức là giữa các món sẽ có mối quan hệ với nhau. Nếu gọi món cơm thì được khuyến mại nước khoáng, còn gọi món phở thì được khuyến mại coca cola. Như vậy ta sẽ có các cặp gọi món với nhau: Cơm + Nước khoáng và Phở + Coca Cola.
Chúng ta cần phải thiết kế sao cho phù hợp với sự khác biệt đó. Sao cho khi khách hàng gọi món cơm thì người phục vụ cần biết phải mang thêm nước khoáng miễn phí, và nếu gọi phở thì phải mang thêm coca cola.
- Vì sao có thể áp dụng mẫu thiết kế Abstract Factory cho bài toán này?
Theo khái niệm thì ta đã xác định được các cặp đối tượng có liên quan nhau (cơm + nước khoáng, phở + coca cola). Và cũng giống với ví dụ trong mẫu thiết kế Factory Method, ta cũng có 2 loại sản phẩm khác nhau, đó là: Đồ ăn và Đồ uống. Nhưng ở đây sẽ phức tạp hơn, ta lại phân loại ra đồ ăn nào sẽ được khuyến mại đồ uống nào.
Như vậy ta cần 1 lớp giao diện chung là nhà máy để định nghĩa việc tạo ra 2 loại sản phẩm (đồ ăn và đồ uống). Đây chính là thành phần Abstract Factory.
Tiếp theo, ta cần triển khai thêm 2 nhà máy khác cho loại đồ ăn và đồ uống theo giao diện chung nhà máy. Đây chính là thành phần Concrete Factory.
Tương tự, ta cũng cần 1 lớp giao diện chung cho sản phẩm mà nhà máy tạo ra. Và ta cũng cần triển khai 2 lớp tương ứng với 2 loại sản phẩm. Đây chính là thành phần Abstract Product/Product.
- Cách triển khai
Ta cần thực hiện đầy đủ theo định nghĩa và các thành phần cần có của mẫu thiết kế này.
Đầu tiên ta sẽ tạo thành phần Abtract Factory. Đây chính là 1 lớp giao diện nhà máy để các lớp con cụ thể hóa nó.
public interface IAbstractFactory
{
IAbstractDoAn TaoDoAn();
IAbstractDoUong TaoDoUong();
}
Tiếp theo, ta sẽ cụ thể hóa nhà máy đó (Concrete Factory) bằng cách triển khai 2 nhà máy về đồ ăn loại cơm và loại phở.
class DoAnLoaiComFactory : IAbstractFactory
{
public IAbstractDoAn TaoDoAn()
{
return new DoAnLoaiCom();
}
public IAbstractDoUong TaoDoUong()
{
return new DoUongNuocKhoang();
}
}
class DoAnLoaiPhoFactory : IAbstractFactory
{
public IAbstractDoAn TaoDoAn()
{
return new DoAnLoaiPho();
}
public IAbstractDoUong TaoDoUong()
{
return new DoUongNuocNgot();
}
}
Việc cụ thể hóa đó sẽ sử dụng giao diện đồ ăn, đồ uống, cùng với việc trả về tương ướng cho mỗi việc triển khai đồ ăn, đồ uống. Đây chính là thành phần Abstract Product/Product.
Như vậy ta sẽ có việc triển khai giao diện đồ ăn và các thực hiện hóa của nó như sau:
public interface IAbstractDoAn
{
string LayDoAn();
string LayDoUongKhuyenMai(IAbstractDoUong doUong);
}
class DoAnLoaiCom : IAbstractDoAn
{
public string LayDoAn()
{
return "Com";
}
public string LayDoUongKhuyenMai(IAbstractDoUong doUong)
{
var doUongKhuyenMai = doUong.LayDoUong();
return doUongKhuyenMai;
}
}
class DoAnLoaiPho : IAbstractDoAn
{
public string LayDoAn()
{
return "Pho";
}
public string LayDoUongKhuyenMai(IAbstractDoUong doUong)
{
var doUongKhuyenMai = doUong.LayDoUong();
return doUongKhuyenMai;
}
}
Như đoạn code trên ta thấy, mỗi loại đồ ăn sẽ đi kèm với đồ khuyến mại khác nhau.
Còn đây sẽ là việc triển khai cho giao diện đồ uống cùng với các thực hiện hóa của nó:
public interface IAbstractDoUong
{
string LayDoUong();
}
class DoUongNuocNgot : IAbstractDoUong
{
public string LayDoUong()
{
return "Coca Cola";
}
}
class DoUongNuocKhoang : IAbstractDoUong
{
public string LayDoUong()
{
return "Nuoc Khoang";
}
}
Cuối cùng là cách mô phỏng việc gọi món của khách hàng. Mỗi món gọi sẽ tương ứng với đồ khuyến mại khác nhau.
class KhachHangAbstractFactory
{
public void Main()
{
Console.WriteLine("Khach hang 1: Toi muon goi mon Com.");
KhachHangGoiMon(new DoAnLoaiComFactory());
Console.WriteLine();
Console.WriteLine("Khach hang 2: Toi muon goi mon Pho.");
KhachHangGoiMon(new DoAnLoaiPhoFactory());
}
public void KhachHangGoiMon(IAbstractFactory factory)
{
var doAn = factory.TaoDoAn();
var doUong = factory.TaoDoUong();
// Nguoi phuc vu thuc hien lay mon an + do khuyen mai
Console.WriteLine("Nguoi phuc vu: Lay do an: " + doAn.LayDoAn());
Console.WriteLine("Nguoi phuc vu: Lay do uong khuyen mai cung voi Pho: " + doAn.LayDoUongKhuyenMai(doUong));
}
}
Kết quả sau khi chạy chương trình:
Kết luận
Như vậy, qua bài viết này, tôi hi vọng các bạn sẽ nắm được khái niệm cũng như biết được việc khi nào thì nên áp dụng mẫu thiết kế Abstract Factory vào trong ứng dụng của mình.
Ví dụ tôi đưa đôi khi không chính xác và thuyết phục hoàn toàn, rất mong sự góp ý từ phía các bạn để tôi có thể củng cố thêm kiến thức cho bản thân mình.
