Tiny Steps

Learn from tiny steps every day, and make a big difference in your journey

Ở bài viết này chúng ta tìm hiểu một Factory Pattern tiếp theo là Abstract Factory nằm trong nhóm Creational Patterns.

Vấn đề

Tiếp tục với ví dụ ở bài trước Factory Method, nhưng hãy tưởng tượng giờ đây bạn không phải là một Rich Kid nữa, bạn là một tỉ phú đi :))), tỉ phú thì không chỉ có một chiếc xe máy hay ô tô như bài trước nữa, mà giờ bạn có hàng chục loại ô tô, xe máy khác nhau, xong ngoài vợ ra thì có rất nhiều bồ đúng không.

Và để tránh bị phát hiện mỗi khi đi với các cô bồ khác nhau thì hàng ngày bạn sẽ phải ngồi nghĩ xem nên đi xe nào với cô bồ nào để tránh bị trùng trong một nhóm các xe mà bạn đang có kiểu như sau:

Vậy với mỗi cô bồ bạn sẽ phải tạo một group Car và Motorbike riêng kiểu như sau:

  • Bồ 1: Car = Lexus, Motorbike = Wakasaki
  • Bồ 2: Car = G63, Motorbike = Ducati
  • Bồ 3: Car = Ferrari, Motorbike = AirBlade
  • Và rất nhiều nhóm nữa

Với cách làmnhư trên sẽ gặp vấn đề gì?

  • Sẽ không phân nhóm nhất định cho Car và Motorbike, dẫn đến mỗi khi chọn sẽ đi chơi với bồ sẽ rất mất thời gian, dễ bị nhầm lẫn giữa các loại xe với nhau (Cái này nguy hiểm nha, nhầm là bị lộ có bồ ngay)
  • Rất khó xử lý và maintain khi có thể các loại xe mới
  • Và quan trong như bài trước về Factory Method, mỗi lần muốn sử dụng sẽ bạn lại phải đau đầu nghĩ xem nên chọn xe gì, đi chơi với bồ nào, rất là mất thời gian và bị lặp lại code ở các modules khác mà bạn sử dụng (Don’t repeat yourself)

Giải pháp

Vậy trong trường hợp này chúng ta hoàn toàn có thể áp dụng Abstract Factory

Định nghĩa: “Provide an interface for creating families of related or dependent objects without specifying their concrete classes.” (trích GOF).

Cụ thể ở đây Abstract Factory giúp chúng ta giải quyết:

  • Thay vì gọi trực tiếp để tạo từng đối tượng nhỏ, Abstract Factory sẽ cung cấp một Interface để tạo họ các đối tượng liên quan hoặc phụ thuộc lẫn nhau. Đơn giản thì nó sẽ nhóm của các đối tượng mà người dùng muốn sử dụng thành một class cho từng mục đích cụ thể và sử dụng qua một Interface đơn giản hơn rất nhiều.
  • Và cũng như ý tưởng chung của Factory Patterns, nó sẽ che giấu việc khởi tạo các objects, phía clients sẽ không cần quan tâm objects được tạo như thế nào. Điều này giúp dễ sử dụng hơn, tránh lặp lại code, dễ maintain
  • Đảm bảo tính nhất quán khi sử dụng, quy định luôn từng group objects cho từng mục đích sử dụng cụ thể khác nhau

Cụ thể như ở bài toán chọn xe như trên, chúng ta sẽ áp dụng Abstract Factory như sau:

Chúng ta sẽ tạo một Abstract Factory Interface cho việc tạo Car và Motorbike

Lúc này chúng ta sẽ tạo luôn 2 groups gồm Car và Motorbike cho mỗi cô bồ luôn, ví dụ:

WifeFactory1: Car = G63, Motorbike=Kawasaki

WifeFactory2: Car = Ferrari, Motorbike=Ducati

Bằng cách này chúng ta vừa có thể che giấu đi việc khởi tạo các objects con, lại phân nhóm các groups khác nhau cho từng cô bồ, và cũng dễ dàng thêm các cô bồ bồ mới bằng việc tạo ra các subclass tương tự như WifeFactory1 và WifeFactory2

Implement Abstract Factory trong C++

Trước tiên chúng tạo tạo các Interfaces Car và Motorbike trước:

  • Car
class ICar{
public:
    ICar(){}
    virtual ~ICar(){}
    virtual void showInfo() = 0;
};

class Lexus : public ICar{
public:
    Lexus(){}
    ~Lexus() {}
    void showInfo() override{
        std::cout << "This is Lexus car\n";
    }
};

class Ferrari : public ICar{
public:
    Ferrari(){}
    ~Ferrari(){}
    void showInfo() override{
        std::cout << "This is Ferrari car\n";
    }
};

class G63 : public ICar{
public:
    G63(){}
    ~G63(){}
    void showInfo() override{
        std::cout << "This is G63 car\n";
    }
};
  • Motorbike
class IMotorbike{
public:
    IMotorbike(){}
    virtual ~IMotorbike(){}
    virtual void showInfo() = 0;
};

class AirBlade : public IMotorbike{
public:
    AirBlade(){}
    ~AirBlade(){}
    void showInfo() override{
        std::cout << "This is AirBlade motorbike\n";
    }
};

class Kawasaki : public IMotorbike{
public:
    Kawasaki(){}
    ~Kawasaki(){}
    void showInfo() override{
        std::cout << "This is Kawasaki motorbike\n";
    }
};

class Ducati : public IMotorbike{
public:
    Ducati(){}
    ~Ducati(){}
    void showInfo() override{
        std::cout << "This is Ducati motorbike\n";
    }
};

Sau đó chúng ta tạo Abstract Factory và tạo ra 2 groups cho 2 cô bồ như sau:

class AbstractFactory{
public:
    AbstractFactory(){}
    virtual ~AbstractFactory() {}
    virtual std::shared_ptr<ICar> createCar() = 0;
    virtual std::shared_ptr<IMotorbike> createMotorbike() = 0;
};

class WifeFactory1 : public AbstractFactory{
public:
    WifeFactory1(){}
    ~WifeFactory1(){}
    std::shared_ptr<ICar> createCar() override{
        return std::make_shared<Lexus>();
    }
    std::shared_ptr<IMotorbike> createMotorbike() override{
        return std::make_shared<AirBlade>();
    }

};

class WifeFactory2 : public AbstractFactory{
public:
    WifeFactory2(){}
    ~WifeFactory2(){}
    std::shared_ptr<ICar> createCar() override{
        return std::make_shared<G63>();
    }
    std::shared_ptr<IMotorbike> createMotorbike() override{
        return std::make_shared<Ducati>();
    }

};

Và giờ việc sử dụng cực kì đơn giản:

int main(){

    // wife 1
    AbstractFactory* wife1 = new WifeFactory1();
    auto car = wife1->createCar();
    auto motor = wife1->createMotorbike();
    car->showInfo();
    motor->showInfo();

    // wife 2
    AbstractFactory* wife2 = new WifeFactory2();
    auto car2 = wife2->createCar();
    auto motor2 = wife2->createMotorbike();
    car2->showInfo();
    motor2->showInfo();

    return 0;
}

Như vậy từ rất nhiều loại Car, Motorbike khác nhau, chúng ta có thể tạo thành các groups cho từng mục đích sử dụng WifeFactory1, WifeFactory2, và sau có thể nhiều hơn nữa WifeFactory2, WifeFactory4, ….

Ứng dụng

  • Chúng ta có thể áp dụng Abstract Factory khi muốn tạo nhiều nhóm các đối tượng có liên quan rằng buộc cho các mục đích sử dụng cụ thể khác nhau
  • Khi muốn đóng tạo một Inferface dễ dàng và đơn giản hơn cho việc tạo các đối tượng mà không cần quan tâm quá nhiều đến các đối tượng cụ thể đó.
  • Muốn tạo một danh sách các tối tượng và interfaces của chúng, nhưng đồng thời lại muốn che giấu đi các implementations

Ưu và nhược điểm

Ưu điểm

  • Đảm bảo mỗi một client sử dụng một nhóm các đối tượng riêng biệt, mục đích khác nhau và không làm ảnh hưởng lẫn nhau
  • Dễ dàng maintain, thêm các groups mới bằng các tạo thêm các Concrete Class từ Abstract Factory
  • Tránh được sự phụ thuộc giữa client và các objects vì lúc nào client chỉ quan tâm đến đúng một factory mà nó sử dụng
  • Đảm bảo tính Open/Closed mỗi khi thêm các đối tượng mới hay các groups mới.

Nhược điểm

  • System có thể trở nên phức tạp hơn khi có quá nhiều Concrete Factory được tạo cho các groups mới

Sự khác nhau giữa Abstract Factory và Factory Method

Như chúng ta đã biết cả 2 dùng cho cho một mục đích là tạo tạo một Interface dễ sử dụng cho việc khởi tạo từ các Objects có sẵn, dễ dàng maintain hơn và đồng thời che giấu đi sự khởi tạo, implementation của các objects, tránh duplicate code. Tuy nhiên chúng ta cần phân biệt rõ 2 loại này để có thể áp dụng chính xác hơn:

Factory Method: Nó chỉ đơn giản là cung cấp một method để quyết định, che giấu sự khởi tạo một objects theo yêu cầu và trả ra đúng một object đó cho phía client

Abstract Factory: Tạo và phân chia một hoặc nhiều objects có liên quan thành các nhóm cho các mục đích sử dụng cụ thể khác nhau mà không hề phụ thuộc vào các objects đó.

Kết luận

Như vậy ở bài viết này, mình đã giới thiệu thêm về một loại Factory Patterns là Abstract Factory. Hi vọng bài viết hữu ích cho các bạn, nếu các bạn thấy bài viết hay thì hãy chia sẻ cho các anh em lập trình khác cùng biết nhé. Cám ơn các bạn đã ghé đọc ^^. Chúc các bạn làm việc hiệu quả ❤

Reference

Book: Design Patterns Elements of Reusable Object-Oriented Software

Book: Head First: Design Patterns

Bình luận về bài viết này