Design Patterns: Factory Method

Factory Method cung cấp một interface để tạo các đối tượng trong một lớp cha, nhưng cho phép các lớp con thay đổi kiểu đối tượng sẽ được tạo.

Mục đích của Factory Method

Factory Method là một creational design pattern cung cấp một interface để tạo các đối tượng trong lớp cha, nhưng cho phép các lớp con thay đổi kiểu đối tượng sẽ được tạo.

Factory Method Design Pattern

Vấn đề

Hãy tưởng tượng rằng bạn đang tạo một ứng dụng quản lý hậu cần. Phiên bản đầu tiên của ứng dụng chỉ có thể xử lý việc vận chuyển bằng xe tải, vì vậy phần lớn mã của bạn nằm trong class Truck.

Sau một thời gian, ứng dụng của bạn trở nên khá phổ biến. Mỗi ngày, bạn nhận được hàng chục yêu cầu từ các công ty vận tải đường biển để kết hợp dịch vụ hậu cần đường biển vào ứng dụng.

Việc thêm một lớp mới vào chương trình không phải là đơn giản nếu phần còn lại của mã đã được ghép nối với các lớp hiện có.
Việc thêm một lớp mới vào chương trình không phải là đơn giản nếu phần còn lại của mã đã được ghép nối với các lớp hiện có.

Tin tuyệt vời phải không? Nhưng làm thế nào bây giờ? Hiện tại, hầu hết mã của bạn được kết hợp với class Truck. Việc thêm class Ships vào ứng dụng sẽ yêu cầu thực hiện các thay đổi đối với toàn bộ mã. Hơn nữa, nếu sau này bạn quyết định thêm một loại phương tiện vận chuyển khác vào ứng dụng, có thể bạn sẽ cần thực hiện lại tất cả những thay đổi này.

Kết quả là, bạn sẽ phải tạo một đoạn mã khá khó chịu với các điều kiện chuyển đổi hành vi của ứng dụng tùy thuộc vào loại phương tiện vận chuyển.

Giải pháp của Factory Method

Mẫu Factory Method gợi ý rằng bạn nên thay thế cách gọi khởi tạo đối tượng trực tiếp (sử dụng toán tử new) bằng cách gọi đến một phương thức factory đặc biệt. Đừng lo lắng: các đối tượng vẫn được tạo thông qua toán tử new, nhưng nó được gọi từ bên trong phương thức gốc. Các đối tượng được trả về theo phương thức factory thường được gọi là product.

Thoạt nhìn, thay đổi này có vẻ vô nghĩa: chúng ta vừa chuyển lời gọi hàm khởi tạo từ phần này sang phần khác của chương trình. Tuy nhiên, hãy xem xét điều này: bây giờ bạn có thể ghi đè phương thức gốc trong một lớp con và thay đổi lớp product đang được tạo bởi phương thức này.

Tuy nhiên, có một hạn chế nhỏ: các lớp con có thể trả về các loại product khác nhau chỉ khi các product này có lớp cơ sở hoặc interface chung. Ngoài ra, phương thức factory trong lớp cơ sở nên có kiểu trả về được khai báo là interface này.

Tất cả các lớp product phải triển khai từ cùng một interface
Tất cả các lớp product phải triển khai từ cùng một interface

Ví dụ, cả hai class TruckShip đều triển khai interface Transport, interface này khai báo một phương thức tên là deliver. Mỗi class triển khai phương thức này khác nhau: xe tải chuyển hàng bằng đường bộ, tàu thủy chuyển hàng bằng đường biển. Phương thức factory trong class RoadLogistics trả về các đối tượng xe tải, trong khi phương thức factory trong class SeaLogistics trả về tàu thủy.

Tất cả các lớp product đều triển khai một interface chung.
Tất cả các lớp product đều triển khai một interface chung.

Mã sử ​​dụng phương thức gốc (thường được gọi là mã client) không thấy sự khác biệt giữa các product thực tế được trả về bởi các lớp con khác nhau. Client coi tất cả các product được triển khai từ interface Transport. Client biết rằng tất cả các đối tượng vận chuyển đều phải có phương thức deliver, nhưng nó không quan tâm chính xác cách thức hoạt động của đối tượng vận chuyển.

Kiến trúc của Factory Method

Kiến trúc của Factory Method
Kiến trúc của Factory Method

Trong đó:

  • Product: là interface chung cho tất cả các đối tượng có thể được tạo ra bởi Creator.
  • Concrete Product: triển khai interface Product.
  • Creator: khai báo phương thức trừu tượng factory trả về interface Product.
  • Concrete Creator: ghi đè phương thức factory gốc để trả về thể hiện của một Concrete Product cụ thể.

Mã giả của Factory Method

Ví dụ này minh họa cách Factory Method có thể được sử dụng để tạo các phần tử giao diện người dùng đa nền tảng mà không cần gắn mã client với các lớp giao diện người dùng cụ thể.

Mã giả của Factory Method

Lớp Dialog cơ sở sử dụng các phần tử giao diện người dùng khác nhau để hiển thị cửa sổ của nó. Trong các hệ điều hành khác nhau, các phần tử này có thể trông hơi khác một chút, nhưng chúng vẫn phải hoạt động nhất quán. Một button trong Windows vẫn là một button trong Linux.

Với Factory Method, bạn không cần phải viết lại logic của hộp thoại cho từng hệ điều hành. Nếu chúng ta khai báo một phương thức factory tạo ra các button bên trong lớp Dialog cơ sở, thì sau này chúng ta có thể tạo một lớp Dialog con trả về các button kiểu Windows từ phương thức gốc. Sau đó, lớp Dialog con kế thừa hầu hết mã của lớp Dialog cơ sở, nhưng nhờ phương thức gốc, có thể hiển thị các button trông giống Windows trên màn hình.

Để mẫu này hoạt động, lớp Dialog cơ sở phải làm việc với các button trừu tượng: một class cơ sở hoặc một interface mà tất cả các button cụ thể đều tuân theo. Bằng cách này, mã của Dialog vẫn hoạt động, cho dù nó hoạt động với loại button nào.

Tất nhiên, bạn cũng có thể áp dụng cách tiếp cận này cho các phần tử giao diện người dùng khác. Tuy nhiên, với mỗi phương thức factory mới bạn thêm vào hộp thoại, bạn sẽ tiến gần hơn đến mẫu Abstract Factory. Đừng sợ, chúng ta sẽ nói về mô hình này sau.

// The creator class declares the factory method that must
// return an object of a product class. The creator's subclasses
// usually provide the implementation of this method.
class Dialog is
    // The creator may also provide some default implementation
    // of the factory method.
    abstract method createButton():Button

    // Note that, despite its name, the creator's primary
    // responsibility isn't creating products. It usually
    // contains some core business logic that relies on product
    // objects returned by the factory method. Subclasses can
    // indirectly change that business logic by overriding the
    // factory method and returning a different type of product
    // from it.
    method render() is
        // Call the factory method to create a product object.
        Button okButton = createButton()
        // Now use the product.
        okButton.onClick(closeDialog)
        okButton.render()


// Concrete creators override the factory method to change the
// resulting product's type.
class WindowsDialog extends Dialog is
    method createButton():Button is
        return new WindowsButton()

class WebDialog extends Dialog is
    method createButton():Button is
        return new HTMLButton()


// The product interface declares the operations that all
// concrete products must implement.
interface Button is
    method render()
    method onClick(f)

// Concrete products provide various implementations of the
// product interface.
class WindowsButton implements Button is
    method render(a, b) is
        // Render a button in Windows style.
    method onClick(f) is
        // Bind a native OS click event.

class HTMLButton implements Button is
    method render(a, b) is
        // Return an HTML representation of a button.
    method onClick(f) is
        // Bind a web browser click event.


class Application is
    field dialog: Dialog

    // The application picks a creator's type depending on the
    // current configuration or environment settings.
    method initialize() is
        config = readApplicationConfigFile()

        if (config.OS == "Windows") then
            dialog = new WindowsDialog()
        else if (config.OS == "Web") then
            dialog = new WebDialog()
        else
            throw new Exception("Error! Unknown operating system.")

    // The client code works with an instance of a concrete
    // creator, albeit through its base interface. As long as
    // the client keeps working with the creator via the base
    // interface, you can pass it any creator's subclass.
    method main() is
        this.initialize()
        dialog.render()

Khả năng áp dụng của Factory Method

Sử dụng Factory Method khi bạn không biết trước chính xác các loại và sự phụ thuộc của các đối tượng mà mã của bạn sẽ hoạt động.

Factory Method phân tách mã xây dựng product với mã thực sự sử dụng product. Do đó, việc mở rộng mã xây dựng product độc lập với phần còn lại của mã sẽ dễ dàng hơn.

Ví dụ: để thêm một loại product mới vào ứng dụng, bạn chỉ cần tạo một lớp Creator con mới và ghi đè phương thức gốc trong đó.

Sử dụng Factory Method khi bạn muốn cung cấp cho người dùng thư viện hoặc framework của mình một cách để mở rộng các thành phần bên trong của nó.

Kế thừa có lẽ là cách dễ nhất để mở rộng hành vi mặc định của một thư viện hoặc framework. Nhưng làm thế nào để framework nhận ra rằng lớp con của bạn nên được sử dụng thay vì một thành phần tiêu chuẩn?

Giải pháp là đưa mã xây dựng các thành phần trong framework thành một phương thức gốc duy nhất và cho phép bất kỳ ai ghi đè phương thức này ngoài việc mở rộng chính thành phần đó.

Hãy xem nó sẽ hoạt động như thế nào. Hãy tưởng tượng rằng bạn viết một ứng dụng bằng cách sử dụng framework giao diện người dùng nguồn mở. Ứng dụng của bạn phải có các button bo tròn góc, nhưng framework chỉ cung cấp các button góc hình vuông. Bạn tạo lớp RoundButton con kế thừa lớp Button tiêu chuẩn. Nhưng bây giờ bạn cần yêu cầu lớp UIFramework chính sử dụng lớp RoundButton mới thay vì lớp mặc định.

Để đạt được điều này, bạn tạo một lớp con UIWithRoundButtons từ một lớp cơ sở và ghi đè phương thức createButton của nó. Trong khi phương thức này trả về các đối tượng của lớp Button cơ sở, bạn làm cho lớp con của mình trả về các đối tượng RoundButton. Bây giờ sử dụng lớp UIWithRoundButtons thay vì UIFramework.

Cách triển khai Factory Method

1. Các product phải triển khai cùng một interface. Interface này khai báo các phương thức có ý nghĩa trong mọi product.

2. Thêm một phương thức gốc trống bên trong lớp Creator. Kiểu trả về của phương thức phải phù hợp với interface product chung.

3. Trong mã của Creator, hãy tìm tất cả các tham chiếu đến constructor của product. Thay thế chúng bằng các lệnh gọi đến phương thức factory.

4. Bây giờ, hãy tạo một tập hợp các lớp con của Creator cho mỗi loại product được liệt kê trong phương thức gốc. Ghi đè phương thức factory trong các lớp con để trả về product tương ứng.

5. Sau cùng, nếu phương thức factory gốc không còn bất kỳ mã gì, bạn có thể chuyển nó thành phương thức trừu tượng. Ngược lại, bạn có thể đặt nó thành hành vi mặc định của phương thức.

Ưu và nhược điểm của Factory Method

Ưu điểm Nhược điểm
  • Bạn tránh được liên kết chặt chẽ giữa Creator và các Concrete Product.
  • Nguyên tắc Single Responsibility. Bạn có thể di chuyển mã tạo Product vào một nơi trong chương trình, giúp mã hỗ trợ dễ dàng hơn.
  • Nguyên tắc Open/Close. Bạn có thể giới thiệu các loại Product mới vào chương trình mà không làm phá vỡ mã client hiện có.
  • Mã có thể trở nên phức tạp hơn vì bạn cần giới thiệu nhiều lớp con mới để triển khai mẫu. Trường hợp tốt nhất là khi bạn giới thiệu mẫu vào một hệ thống phân cấp hiện có của các lớp Creator.

Mối quan hệ với các Design Patterns 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).
  • Các lớp Abstract Factory thường dựa trên một tập hợp các Factory Method, nhưng bạn cũng có thể sử dụng Prototype để tạo các phương thức trên các lớp này.
  • Bạn có thể sử dụng Factory Method cùng với Iterator để cho phép các lớp con của tập hợp trả về các kiểu vòng lặp khác nhau tương thích với tập hợp.
  • Prototype không dựa trên sự kế thừa, vì vậy bản thân nó không có nhược điểm. Mặt khác, Prototype yêu cầu khởi tạo đối tượng nhân bản phức tạp. Factory Method dựa trên sự kế thừa nhưng không yêu cầu bước khởi tạo.

Ví dụ về Factory Method

Triển khai Factory Method trong C#

Độ phức tạp: 2/5

Độ phổ biến: 5/5

Ví dụ về cách sử dụng: Mẫu Factory Method được sử dụng rộng rãi trong C#. Nó rất hữu ích khi bạn cần cung cấp mức độ linh hoạt cao cho mã của mình.

Nhận biết: Mẫu Factory Method có thể được nhận biết bởi phương thức tạo, phương thức này tạo ra các đối tượng từ các lớp cụ thể, nhưng trả về chúng dưới dạng các đối tượng thuộc kiểu trừu tượng hoặc interface.

Ví dụ sau minh họa cấu trúc của mẫu Factory Method. Nó tập trung vào việc trả lời những câu hỏi sau:

  • Nó bao gồm những lớp nào?
  • Các lớp này có vai trò gì?
  • Các thành phần của mẫu có liên quan với nhau theo cách nào?
using System;

namespace RefactoringGuru.DesignPatterns.FactoryMethod.Conceptual
{
    // The Creator class declares the factory method that is supposed to return
    // an object of a Product class. The Creator's subclasses usually provide
    // the implementation of this method.
    abstract class Creator
    {
        // Note that the Creator may also provide some default implementation of
        // the factory method.
        public abstract IProduct FactoryMethod();

        // Also note that, despite its name, the Creator's primary
        // responsibility is not creating products. Usually, it contains some
        // core business logic that relies on Product objects, returned by the
        // factory method. Subclasses can indirectly change that business logic
        // by overriding the factory method and returning a different type of
        // product from it.
        public string SomeOperation()
        {
            // Call the factory method to create a Product object.
            var product = FactoryMethod();
            // Now, use the product.
            var result = "Creator: The same creator's code has just worked with "
                + product.Operation();

            return result;
        }
    }

    // Concrete Creators override the factory method in order to change the
    // resulting product's type.
    class ConcreteCreator1 : Creator
    {
        // Note that the signature of the method still uses the abstract product
        // type, even though the concrete product is actually returned from the
        // method. This way the Creator can stay independent of concrete product
        // classes.
        public override IProduct FactoryMethod()
        {
            return new ConcreteProduct1();
        }
    }

    class ConcreteCreator2 : Creator
    {
        public override IProduct FactoryMethod()
        {
            return new ConcreteProduct2();
        }
    }

    // The Product interface declares the operations that all concrete products
    // must implement.
    public interface IProduct
    {
        string Operation();
    }

    // Concrete Products provide various implementations of the Product
    // interface.
    class ConcreteProduct1 : IProduct
    {
        public string Operation()
        {
            return "{Result of ConcreteProduct1}";
        }
    }

    class ConcreteProduct2 : IProduct
    {
        public string Operation()
        {
            return "{Result of ConcreteProduct2}";
        }
    }

    class Client
    {
        public void Main()
        {
            Console.WriteLine("App: Launched with the ConcreteCreator1.");
            ClientCode(new ConcreteCreator1());
            
            Console.WriteLine("");

            Console.WriteLine("App: Launched with the ConcreteCreator2.");
            ClientCode(new ConcreteCreator2());
        }

        // The client code works with an instance of a concrete creator, albeit
        // through its base interface. As long as the client keeps working with
        // the creator via the base interface, you can pass it any creator's
        // subclass.
        public void ClientCode(Creator creator)
        {
            // ...
            Console.WriteLine("Client: I'm not aware of the creator's class," +
                "but it still works.\n" + creator.SomeOperation());
            // ...
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            new Client().Main();
        }
    }
}

Kết quả:

App: Launched with the ConcreteCreator1.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of ConcreteProduct1}

App: Launched with the ConcreteCreator2.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of ConcreteProduct2}
Design PatternLập Trình C#
Bài Viết Liên Quan:
Design Patterns: Visitor
Trung Nguyen 15/01/2021
Design Patterns: Visitor

Visitor là một mẫu thiết kế hành vi cho phép bạn tách các thuật toán khỏi các đối tượng mà chúng hoạt động.

Design Patterns: Template Method
Trung Nguyen 15/01/2021
Design Patterns: Template Method

Template Method định nghĩa khung của một thuật toán trong lớp cha cho phép lớp con ghi đè các bước cụ thể của thuật toán mà không thay đổi cấu trúc của nó.

Design Patterns: Strategy
Trung Nguyen 15/01/2021
Design Patterns: Strategy

Strategy cho phép bạn định nghĩa một nhóm thuật toán, đặt mỗi thuật toán vào một lớp riêng biệt và làm cho các đối tượng của chúng có thể hoán đổi cho nhau.

Design Patterns: State
Trung Nguyen 15/01/2021
Design Patterns: State

State là một mẫu thiết kế hành vi cho phép một đối tượng thay đổi hành vi của nó khi trạng thái bên trong của nó thay đổi.