Hotline
0388011834
Trang chủ/ Góc lập trình/Design Patterns

Creational Patterns - Factory Method



Factory Method

Khái niệm

Là một mẫu thiết kế thuộc nhóm Creational (khởi tạo), cung cấp một giao diện (interface) hoặc phương thức chung để tạo đối tượng, nhưng cho phép lớp con quyết định lớp cụ thể nào sẽ được khởi tạo. Nó thay thế việc sử dụng từ khóa new trực tiếp bằng một phương thức nhà máy (factory method) để giảm sự phụ thuộc và tăng tính linh hoạt.

  •  

Các thành phần chính

  • Product: Định nghĩa giao diện cho đối tượng sản phẩm mà Factory Method tạo ra.
  • Concrete Product: Triển khai chi tiết của Product.
  • Creator (Factory): Khai báo phương thức factory, trả về một đối tượng Product.
  • Concrete Creator: Ghi đè (override) phương thức factory để tạo ra sản phẩm cụ thể.

Ưu điểm

Tăng tính linh hoạt và mở rộng: Dễ dàng thêm loại sản phẩm mới (ConcreteProduct) mà không làm hỏng code hiện có (Nguyên tắc Đóng-Mở: Open/Closed Principle).

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.

Tránh ghép nối (Loose Coupling): Code của bạn không bị phụ thuộc chặt chẽ vào các lớp cụ thể, chỉ phụ thuộc vào interface/abstract class.

Nhược điểm

Code sẽ trở nên phức tạp hơn khi cần triển khai nhiều lớp con.

Ví dụ thực tế

  • Bài toán

Bạn đang xây dựng một ứng dụng quản lý nhà hàng, bạn cần triển khai chức năng phục vụ cho việc gọi món của khách hàng.

Ban đầu bạn chỉ hỗ trợ chức năng gọi đồ uống. Bạn không chắc sau này có hỗ trợ thêm chức năng cho việc gọi gì đó ngoài đồ uống không, ví dụ: Đồ ăn, v.v….

Lúc này bạn sẽ đặt câu hỏi là sẽ viết code thế nào để sau này sẽ không bị ảnh hưởng nhiều bởi việc thêm tính năng đó.

  • Phân tích bài toán

Các tác nhân tham gia: Khách hàng (người gọi món), Người phục vụ (phục vụ khách hàng, ghi lại các món mà khách hàng gọi).

Khách hàng và Người phục vụ giao tiếp với nhau thông qua việc gọi món.

Món sẽ có nhiều loại khác nhau, như là: Đồ uống, đồ ăn.

Khách hàng có thể gọi cùng lúc cả đồ uống và đồ ăn, họ không quan tâm người phục vụ sẽ lấy đồ uống ở đâu hay nấu món ăn thế nào.

Người phục vụ có trách nhiệm ghi lại các món mà khách hàng gọi, sau đó phân loại chúng. Ví dụ: Đồ ăn thì cần thông báo cho đầu bếp để nấu nướng, còn đồ uống thì họ có thể trực tiếp lấy ở tủ lạnh chẳng hạn.

Như vậy nếu khách hàng gọi nhiều loại món khác nhau thì người phục vụ cũng phải thực hiện nhiều công việc khác nhau để lấy đồ cho khách.

  • Vì sao có thể áp dụng mẫu thiết kế Factory Method cho bài toán này?

Đối chiếu với các thành phần chính của mẫu thiết kế Factory Method, ta thấy:

Product: Chính là món, là sản phẩm mà nhà máy sẽ tạo ra.

Creater: Chính là người phục vụ, sẽ là nhà máy tạo ra sản phẩm và đưa tới khách hàng.

Nhà máy là nơi tạo ra sản phẩm. Ta không cần quan tâm có những loại nhà máy nào, chúng ta chỉ cần quan tâm có những loại sản phẩm nào được tạo ra từ nhà máy.

Như vậy ở đây ta sẽ có nhà máy và sản phẩm tạo ra sẽ là đồ uống, đồ ăn.

Vì vậy, ta có thể sử dụng mẫu thiết kế Factory Method để áp dụng cho bài toán này.

  • Cách triển khai

Theo định nghĩa, ta cần cung cấp một giao diện (interface) hoặc phương thức chung để tạo đối tượng. Vậy ta sẽ tạo một giao diện sản phẩm để phục vụ cho việc tạo các đối tượng khác nhau như là: Đồ uống, đồ ăn. Đây chính là thành phần Product.

    public interface ISanPham

    {

        string GoiMon();

    }

Tiếp theo ta sẽ cho phép lớp con quyết định lớp cụ thể nào sẽ được khởi tạo. Như vậy ta sẽ tạo 2 lớp con tương ứng với Đồ uống, Đồ ăn. Hai lớp con đó sẽ kế thừa giao diện sản phẩm. Đây chính là thành phần Concrete Product.

    class DoUong : ISanPham
    {
        public string GoiMon()
        {
            return "Khach goi do uong";
        }
    }
 
    class DoAn : ISanPham
    {
        public string GoiMon()
        {
            return "Khach goi do an";
        }
    }

Sang thành phần quan trọng là Creator (Factory), đây chính là người phục vụ, sẽ trả về sản phẩm để mang tới cho khách hàng.

    abstract class NguoiPhucVu
    {
        public abstract ISanPham FactoryMethod();
 
        public string PhucVu()
        {
            var sanPham = FactoryMethod();
            var result = "Nguoi phuc vu: Dang chuan bi theo thuc don cua " + sanPham.GoiMon();
            return result;
        }
    }

Ta sẽ khai báo một phương thức nhà máy để trả về sản phẩm. Phương thức này sẽ được khai báo trừu tượng để có thể ghi đè ở các lớp con.

Và khai báo thêm một phương thức PhucVu() để lấy ra sản phẩm cụ thể.

Cuối cùng là thành phần Concrete Creator, thành phần này sẽ thực hiện hóa các công việc lấy đồ ăn hay lấy đồ uống của người phục vụ. Nó sẽ ghi đè phương thức nhà máy ở trên để trả về các sản phẩm cụ thể.

    class NguoiPhucVuDoUong : NguoiPhucVu
    {
        public override ISanPham FactoryMethod()
        {
            return new DoUong();
        }
    }
 
    class NguoiPhucVuDoAn : NguoiPhucVu
    {
        public override ISanPham FactoryMethod()
        {
            return new DoAn();
        }
    }

Và đây sẽ là đoạn code mô phỏng việc gọi món của khách hàng. Giả sử họ gọi đồ ăn và đồ uống (sản phẩm) từ 1 người phục vụ (nhà máy).

    class KhachHang
    {
        public void Main()
        {
            NguoiPhucVu nguoiPhucVu = new NguoiPhucVuDoUong();
            Console.WriteLine(nguoiPhucVu.PhucVu());
 
            Console.WriteLine("Khach goi tiep mon...");
 
            nguoiPhucVu = new NguoiPhucVuDoAn();
            Console.WriteLine(nguoiPhucVu.PhucVu());
        }
    }

Kết quả khi chạy chương trình sẽ như sau:

Người phục vụ (nhà máy) sẽ trả về khách hàng đồ ăn và đồ uống (sản phẩm) mà khách hàng đã gọi.

    1.  

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ế Factory Method 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.