Factory Method Pattern – Giải thích đơn giản, dễ hiểu

Đã lâu không quay trở lại với series design pattern. Tiếp tục với Creational Pattern (pattern khởi tạo), đi cho hết luôn cả luồng. Bài viết này giới thiệu với anh em pattern Factory Method, một pattern khá nổi tiếng, được nhiều anh em biết tới.

Anh em ready chưa, bắt đầu ngay thôi!

1. Định nghĩa Factory Method pattern

Rất chi là khó hiểu nhưng luôn luôn là định nghĩa về pattern. Nếu anh em mới tìm hiểu về pattern này lần đầu thì đừng lo. Cứ lướt qua định nghĩa, đi xuống ví dụ, từng từng một sẽ rõ ràng ra.

Sau khi đọc hết và có cái hiểu cơ bản về pattern, quay lại đọc khái niệm đôi khi lại vỡ lẽ ra nhiều điều.

Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created. Factory method là design pattern khởi tạo cung cấp interface để khởi tạo các đối tượng (object) ở superclass, nhưng cho phép class phụ (subclasses) thay đổi kiểu của object sẽ được tạo ra.

Nghe ảo ma canada chưa, khởi tạo ở superclass nhưng lại cho phép thay đổi được kiểu của object cho phù hợp với subclass của mình. Vậy Factory Method pattern đã làm điều đó như thế nào?

  Các loại Design patterns
  Adapter Pattern – Xoá nhoà khác biệt

2. Đặt vấn đề

Giả sử như anh em đang khởi tạo một ứng dụng liên quan tới giao vận (cũng có thể gọi là hệ thống quản lý giao vận – logistics management application). Đầu tiên, tiền còn ít nên anh em chỉ cố gắng xoay xở gom góp mua được con xe tải.

Quản lý giao vận bằng xe tải (truck), anh em sẽ tạo Truck class.

Một thời gian sau, các nhà đầu tư bần bần nhảy vô (nhầm, các agents) nhảy vào, ứng dụng của anh em trở nên phổ biến hơn cả Ahamove, Ninja Van. Một ông khách hàng to bự, bay vào nhờ anh em quản lý thêm giao vận trên biển (sea logistics).

Ngon, business phát triển rồi, nhưng giờ code thì sao?. Truck class thì viết đầy vào rồi, mà thêm Ship vào thì thay đổi mất code base. Mà không chỉ có mỗi sea, sau này còn có mở thị trường giao vận hàng không thì sao?.

factory method

3. Giải quyết với Factory Method pattern

Với Factory Method pattern, với bài toán ở trên, anh em mong muốn xây dựng một class sao cho cả Truck, Sea hoặc Airplane sau này có thể kế thừa. Mỗi lần xử lý đơn hàng là mỗi lần tạo transport.

Vậy phương thức createTransport sẽ tạo ra object chính xác cho từng loại hình vận chuyển.

  • createTransport ở RoadLogistics sẽ return new Truck()
  • createTransport ở SeaLogistics sẽ return new Ship()

Phương thức createTransport sẽ nằm ở SuperClass, subclass (Road, Sea) lúc này trả về các kiểu khác nhau.

factory method

Constructor khởi tạo lúc này di chuyển về Factory Method class (superclass), các class con thực hiện override lại, tất cả các phương thức vận chuyển đều sử dụng chung một interface, cái khác là kiểu trả về cho từng class

factory method

Ví dụ, cả Truck (xe tải) và Ship (thuyền) đều implement chung Transport interface. Trong interface rõ ràng khai báo method deliver(). Nhưng mỗi class lai hiện thức method này ở class của mình khác nhau. Truck thì di chuyển trên đường bộ, Ship thì di chuyển trên đường sông.

Factory Method lúc này, sau khi khởi tạo constructor ở class RoadLogistics sẽ trả về truck object, còn ShipLogistics sẽ trả về ship object. Rõ ràng nếu làm được vậy thì sau này có airplane hay hàng không vũ trụ gì cũng không là vấn đề.

factory method Tất cả các class đều implement chung một common interface, với Factory Method, ta pass các object cho đúng đối tượng nhưng không làm phá vỡ đi code base của Factory.

4. Thực tế giải quyết bài toán

Rồi, giờ triển khai cụ thể với class diagram

factory method
  • Số 1: Product sẽ khai báo interface, cái này là common interface tất cả các class đều sử dụng. doStuff() đây anh em có thể hiểu là vận chuyển, xác nhận đơn, giao thành công hoặc bất cứ thứ gì khác. Nói chung là có giao vận bằng hình thức nào thì nó cũng có các phương thức cần implement.
  • Số 2: Concrete ProductA và Concrete ProductB sẽ implement inferface common, cụ thể làm gì thì viết vào đây. Ví dụ thuyền thì số km tính bằng hải lý, còn đường bộ thì tính bằng km
  • Số 3: Class Creator sẽ khai báo các factory method trả về new product objects. Kiểu trả về ở đây khớp với Product interface là ok
  • Số 4: Concrete Creators sẽ override các method base ở Factory với các kiểu trả về khác nhau.

5. Code ví dụ

Ví dụ giờ mình dùng cái Factory Method pattern để tạo một cái UI cross platform (đi qua nhiều platform). Cái UI này có một nút nhấn, từ đó mở được 2 loại:

  • Web dialog
  • Windows dialog
factory method

Lúc này ở interface sẽ có 2 method là render(), show cái button ra. Cái thứ hai là ấn vào để mở dialog.

Ở GUIFactory.java ta define interface tạo button và click. Render đổi qua thành createButton nha anh em. Đây là 2 common interface.

package refactoring_guru.abstract_factory.example.factories;

import refactoring_guru.abstract_factory.example.buttons.Button;
import refactoring_guru.abstract_factory.example.checkboxes.Checkbox;

/**
 * Abstract factory knows about all (abstract) product types.
 */
public interface GUIFactory {
    Button createButton();
    Button onClick();
}

Kế đến là MacOS (giả cái sử như là MacOS) có đi nha. Cái này sẽ implement GUIFactory.

package refactoring_guru.abstract_factory.example.factories;

import refactoring_guru.abstract_factory.example.buttons.Button;
import refactoring_guru.abstract_factory.example.buttons.MacOSButton;
import refactoring_guru.abstract_factory.example.checkboxes.Checkbox;
import refactoring_guru.abstract_factory.example.checkboxes.MacOSCheckbox;

/**
 * Each concrete factory extends basic factory and responsible for creating
 * products of a single variety.
 */
public class MacOSFactory implements GUIFactory {

    @Override
    public Button createButton() {
        return new MacOSButton();
    }

    @Override
    public function onClick() {
        return smt with click event
    }
}

Phía Windows factory cũng tương tự

package refactoring_guru.abstract_factory.example.factories;

import refactoring_guru.abstract_factory.example.buttons.Button;
import refactoring_guru.abstract_factory.example.buttons.WindowsButton;
import refactoring_guru.abstract_factory.example.checkboxes.Checkbox;
import refactoring_guru.abstract_factory.example.checkboxes.WindowsCheckbox;

/**
 * Each concrete factory extends basic factory and responsible for creating
 * products of a single variety.
 */
public class WindowsFactory implements GUIFactory {

    @Override
    public Button createButton() {
        return new WindowsButton();
    }

    @Override
    public function onClick() {
        return smt with click event
    }
}
Footer

Xong phần Factory, giờ là lúc hiện thực Button cho từng class. Button là common interface có method paint() để render button.

package refactoring_guru.abstract_factory.example.buttons;

/**
 * Abstract Factory assumes that you have several families of products,
 * structured into separate class hierarchies (Button/Checkbox). All products of
 * the same family have the common interface.
 *
 * This is the common interface for buttons family.
 */
public interface Button {
    void paint();
}
Footer

Lúc này ở MacButton thì khoẻ rồi. Override method và implement những gì mình thích thôi

package refactoring_guru.abstract_factory.example.buttons;

/**
 * All products families have the same varieties (MacOS/Windows).
 *
 * This is a MacOS variant of a button.
 */
public class MacOSButton implements Button {

    @Override
    public void paint() {
        System.out.println("You have created MacOSButton.");
    }
}

Xong xuôi đâu vào đấy thì ở application còn làm gì nữa đâu. Cứ gọi factory là trả ra đúng object của button cần tạo, thích button thì button, thích checkbox thì có checkbox. Rồi từ đó cứ gọi ra các method implement là xong thôi.

package refactoring_guru.abstract_factory.example.app;

import refactoring_guru.abstract_factory.example.buttons.Button;
import refactoring_guru.abstract_factory.example.checkboxes.Checkbox;
import refactoring_guru.abstract_factory.example.factories.GUIFactory;

/**
 * Factory users don't care which concrete factory they use since they work with
 * factories and products through abstract interfaces.
 */
public class Application {
    private Button button;
    private Checkbox checkbox;

    public Application(GUIFactory factory) {
        button = factory.createButton();
        checkbox = factory.createCheckbox();
    }

    public void paint() {
        button.paint();
        checkbox.paint();
    }
}
Footer

6. Ưu nhược điểm

  • Đầu tiên là với Factory pattern anh em sẽ hạn chế đụng độ giữa các class.
  • Mỗi product giờ nằm ở một nơi, chia ra rõ ràng, dễ dàng hơn để maintain sau này.
  • Open,close principles: Anh em thoải mái tạp thêm creator mới mà không cần đụng tới các methods, class cũ đã implement. ít bug hơn.
  • Sử dụng Factory pattern đôi khi làm cho code trở nên khó hiểu hơn, phức tạp. Trường hợp có nhiều subclass.

7. Tham khảo

Thank you for your time – Happy coding!

Tác giả: Kiên Nguyễn

Cảm ơn anh em đã dành thời gian để đọc bài. Anh em có thể tham khảo thêm tin tuyển dụng IT trên TopDev để phát triển sự nghiệp.

Xem thêm:

Một số custom hooks hay sử dụng cho React

AngularJS Là Gì? Khác Biệt Nào Giữa Angular Và Frontend Framework Khác

Mediator Design Pattern – Collaborate via me