Giới thiệu Abstract Factory Pattern

153

Bài viết được sự cho phép của tác giả Nguyễn Văn Minh

Pattern thứ hai mà mình muốn giới thiệu chính là Abstract Factory. Nó có thể được hình dung như một nhà máy lớn, bên trong có các nhà máy nhỏ hơn sản xuất ra những loạt sản phẩm liên quan đến nhau.

Hãy lấy một hãng sản xuất ô tô làm ví dụ, chẳng hạn Hyundai. Họ có nhà máy, hoặc xưởng, chế tạo bánh xe: bánh của Azera, bánh của Sonata, bánh của Veloster, v.v… Đến lượt cửa xe, cũng có nhà máy chế tạo cửa Azera, cửa Sonata, cửa Veloster. Thân xe, động cơ, đèn, và các thành phần khác có những nhà máy chế tạo chúng.

  Design pattern là gì? Tại sao nên sử dụng Design pattern?
  Hướng dẫn sử dụng Factory trong Design Pattern

Vậy phải tổ chức việc sản xuất ấy như thế nào? Cùng theo dõi tiếp nhé!

Nếu mới làm quen với Design Pattern, có thể bạn sẽ muốn đọc bài tổng quan của mình tại đây hoặc bài về Singleton tại đây.

ABSTRACT FACTORY

Giống như ở bài trước về Singleton, bài này gồm 5 phần:

  • Ý tưởng chính
  • Vấn đề cần giải quyết
  • Cấu trúc
  • Code ví dụ
  • Lưu ý
  • Vài lời bình luận

Ý tưởng chính

  • Khai báo một interface với các phương thức tạo các đối tượng abstract.
  • Một hệ thống phân cấp với các “dòng xe” và các “bộ phận xe”.
  • Tránh sử dụng từ khoá new.

Vấn đề cần giải quyết

Khi bạn muốn phần mềm của mình có thể được triển khai trên nhiều nền tảng khác nhau, bạn phải tìm cách xử lý việc khởi tạo các đối tượng trên các nền tảng đó. “Nền tảng” ở đây có thể hiểu là hệ điều hành, cơ sở dữ liệu,… Trong ví dụ ở phần mở đầu, Azera, Sonata và Veloster chính là các nền tảng.

Tuy nhiên, nếu bạn xử lý chuyện này bằng những câu if ... else thì chẳng mấy chốc code của bạn sẽ thành một mớ bòng bong vì phần mềm sẽ dần nở to. Và đó là lý do mà Abstract Factory ra đời.

Cấu trúc

Ta cần có năm thành phần, đó là:

  • Abstract Platform: interface hoặc abstract class với các phương thức tạo ra các “sản phẩm”
  • Concrete Platforms: Khai báo cụ thể các “nền tảng” (PlatformOne, PlatformTwo trong hình)
  • Abstract Products: interface hoặc abstract class định nghĩa các loại “sản phẩm” (AbstractProductOne, AbstractProductTwo trong hình)
  • Concrete Products: Khai báo cụ thể các “sản phẩm” (ProductOnePlatformOne, ProductOnePlatformTwo, ProductTwoPlatformOne, ProductTwoPlatformTwo trong hình)
  • Client: Đối tượng sử dụng Abstract Platform và Abstract Product
Giới thiệu Abstract Factory Pattern

Với ví dụ về việc sản xuất xe, biểu đồ có thể trở thành như sau.

Giới thiệu Abstract Factory Pattern

Bạn cũng cần kiểm tra các tiêu chí dưới đây.

  • Hãy xem xét liệu việc độc lập với các “nền tảng” và việc khởi tạo các đối tượng có phải là vấn đề của hệ thống hiện tại hay không.
  • Làm rõ đâu là “nền tảng”, đâu là “sản phẩm”, và mối quan hệ giữa chúng.
  • Định nghĩa một factory (interface) với đầy đủ các phương thức khởi tạo từng sản phẩm.
  • Định nghĩa các lớp dẫn xuất của interface trên, đảm bảo đã “bao gói” các phương thức khởi tạo (ứng với việc gọi new).
  • Client không được dùng new mà phải dùng các phương thức mà bạn đã khai báo ở interface.

Code ví dụ

Dưới đây là code ví dụ về việc khởi tạo các loại CPU, MMU, viết bằng ngôn ngữ Java.

Các “sản phẩm” (CPU, MMU)
// class CPU
abstract class CPU {}

// class EmberCPU
class EmberCPU extends CPU {}

// class EnginolaCPU
class EnginolaCPU extends CPU {}

// class MMU
abstract class MMU {}

// class EmberMMU
class EmberMMU extends MMU {}

// class EnginolaMMU
class EnginolaMMU extends MMU {}
Các “nền tảng” cụ thể
// class EmberFactory
class EmberToolkit extends AbstractFactory {
    @Override
    public CPU createCPU() {
        return new EmberCPU();
    }

    @Override
    public MMU createMMU() {
        return new EmberMMU();
    }
}

// class EnginolaFactory
class EnginolaToolkit extends AbstractFactory {
    @Override
    public CPU createCPU() {
        return new EnginolaCPU();
    }

    @Override
    public MMU createMMU() {
        return new EnginolaMMU();
    }
}
“Nền tảng” trừu tượng
enum Architecture {
    ENGINOLA, EMBER
}

abstract class AbstractFactory {
    private static final EmberToolkit EMBER_TOOLKIT = new EmberToolkit();
    private static final EnginolaToolkit ENGINOLA_TOOLKIT = new EnginolaToolkit();

    // Returns a concrete factory object that is an instance of the
    // concrete factory class appropriate for the given architecture.
    static AbstractFactory getFactory(Architecture architecture) {
        AbstractFactory factory = null;
        switch (architecture) {
            case ENGINOLA:
                factory = ENGINOLA_TOOLKIT;
                break;
            case EMBER:
                factory = EMBER_TOOLKIT;
                break;
        }
        return factory;
    }

    public abstract CPU createCPU();

    public abstract MMU createMMU();
}
Client
public class Client { public static void main(String[] args) { AbstractFactory factory = AbstractFactory.getFactory(Architecture.EMBER); CPU cpu = factory.createCPU(); } }

Lưu ý

Creational patterns có thể kết hợp với nhau hoặc thay thế cho nhau. Và để quyết định dùng pattern nào hoặc kết hợp như thế nào, bạn phải xem xét trường hợp của mình.

Abstract Factory thường được kết hợp với Factory Method, đôi khi là cả Protoype nữa.

Abstract Factory có thể thay thế cho Facade để che giấu những lớp được đặc tả riêng cho từng “nền tảng”.

Phân biệt Abstract Factory và Builder. Trong khi Abstract Factory chú trọng việc khởi tạo những nhóm đối tượng có liên quan đến nhau (chiều ngang); thì Builder chú trọng vào việc tạo ra một đối tượng qua nhiều bước nối tiếp nhau, giống như một dây chuyền vậy (chiều dọc). Trường hợp của Abstract Factory, đối tượng được trả về ngay; còn trong trường hợp của Builder, đối tượng là kết quả của cả quá trình.

Thông thường, việc thiết kế hệ thống sẽ đi từ Factory Method (dễ hiểu, dễ tuỳ biến) thành Abstract Factory, Prototype, hoặc Builder (mềm dẻo hơn, phức tạp hơn) nếu như người thiết kế cảm thấy tính mềm dẻo là cần thiết.

Vài lời bình luận

Như đã nói ở trên, các creational pattern có thể thay thế cho nhau hoặc kết hợp với nhau tuỳ hoàn cảnh, và việc thiết kế có thể đi từ Factory Method đến Abstract Factory. Tức là bạn phải xem xét thật kỹ càng bài toán của mình để biết cần lựa chọn giải pháp nào, thay vì vội vàng lựa chọn.

Các pattern được đưa ra rõ ràng là đem lại cái lợi lớn cho các lập trình viên nhưng bạn không nên lệ thuộc vào chúng. Theo tôi, đừng bao giờ nên giả định rằng: “Chắc tương lai hệ thống sẽ phải thế này, thế kia” hay “Có lẽ sau này hệ thống cần phải mềm dẻo hơn, flexible hơn nên phải chọn Abstract Factory”. Code ít hơn bao giờ cũng tốt hơn.


Tham khảo

Abstract Factory Design Pattern, Source Making

Hướng dẫn Java Design Pattern – Abstract Factory, GP Coder