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 sẽ tìm một trong những Creational Patterns rất đơn giản mà lại được sử dụng rất rộng dãi là Factory Patterns.

Có 2 hai loại Factory Patterns là Factory Method và Abstract Factory. Phần này chúng ta sẽ tìm hiểu chi tiết về Factory Method thôi nhé. Còn phần 2 mình sẽ giới thiệu chi tiết hơn về Abstract Factory và phân biệt giữa 2 loại Patterns này.

Vấn đề

Bây giờ hãy thử tưởng tượng bạn là một Rich Kid nhé, bố bạn của đầu tư cho bạn rất nhiều loại xe để đi tán gái, nào là Ô tô, xe máy, xe đạp,… các thể loại. Và cô bạn gái của bạn khá nhõng nhõe, một ngày đẹp trời bạn gái của bạn bảo:

“Ứ ừ, nay trời đẹp em thích anh đèo em bằng xe đạp cơ”,

Một ngày khác lại bảo:

“Thôi nay trời hơi mưa anh đi ô tô nhé”

Mỗi lần như thế bạn khá mệt mỏi với việc chọn xem nên đi chơi với người yêu bằng xe gì, sau nhiều lần thì bạn bắt đầu nắm bắt được tâm lí bạn gái, bạn lên một quy luật chọn xe đi chơi. Và nếu phải lập trình bạn sẽ làm như sau:

Thông thường chúng ta sẽ design một Interface IVehicle và các loại xe mà bạn có sẽ mở rộng từ Interface này như sau:

Ok, rồi mỗi khi đi chơi với bạn gái, bạn sẽ phải chọn xe theo thời tiết kiểu như:

std::string weather;
    IVehicle* vehicle = nullptr;
    if(weather == "rainy"){
        vehicle = new Car();
    }
    else if (weather == "sunny"){
        vehicle = new Motorbike();
    }
    else if (weather == "windy"){
        vehicle = new Bicycle();
    } 

Thiết kết theo các trên có đúng không?

Câu trả lời là hoàn toàn chính xác, đúng yêu cầu mà bạn đặt ra

Nhưng vấn đề là bạn sẽ phải lặp lại đoạn code này mỗi lần đi chơi với bạn gái, việc đó khá cồng kềnh đúng không nào. Khi đó bạn đã vi phạm một điều tối kị trong lập trình và thiết kế phần mềm đó là Don’t repeat yourself, và rất khó maintain vì sẽ ảnh hưởng modules khác.

Giải pháp

Vậy để giải quyết vấn đề trên, các bạn hoàn toàn có thể áp dụng Factory Method

Định nghĩa: “Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.” (trích Design Patterns Elements of Reusable Object-Oriented Software)

Ý tưởng chính của Factory Method đó là tạo một class Factory cho việc quyết định xem object nào sẽ được khởi tạo, và khởi tạo như thế nào. Đơn giản thì nó sẽ nhóm tất cả các logics khởi tạo objects vào một class, khi đó người dùng chỉ cần khởi tạo và dùng objects thông qua class đó, mà không cần quan tâm quá nhiều đến cách objects được khởi tạo.

Cụ thể thì Method Factory giúp:

  • Tạo một class interface giúp định nghĩa việc khởi tạo các objects liên quan, phân chia các operations của mỗi class
  • Quyết định việt khởi tạo các objects, khi đó người dùng sẽ không cần quan tâm objects được khởi tạo như thế nào, mà chỉ cần truyền đúng loại object mà họ mong muốn như đã định nghĩa trước.
  • Dễ bảo trì, khi có thêm các objects mới thì chỉ cần sửa class và thêm vào class Factory Method mà không quan tâm hay làm ảnh hưởng đến các modules khác trong hệ thống.

Ok, từ những ý tưởng này, giờ chúng ta thiết kế lại bài toán chọn xe đi chơi với người yêu như sau:

Như các bạn thấy, giờ chúng ta có một VehicleFactory class, lúc này Client chính là bạn hoặc bạn gái sẽ không cần gọi đống code cồng kềnh bên trên nữa mà chỉ cần gọi createVehicle là xong. Nó giúp bạn tránh lặp lại đoạn code kia hằng này, và nếu bạn có thêm xe mới bạn chỉ cần định nghĩa thêm vào class VehicleFactory, điều này giúp code dễ maintain hơn rất nhiều

Implement Factory Method trong C++

Bây giờ thử implement ý tưởng trên bằng C++ nhé

Đầu tiên chúng ta, định nghĩa một IVehicle Interface để các loại xe sẽ implement, giả sử mình có 3 loại xe Car, Motorbike, và Bicyle như sau:

class IVehicle{
public:
    IVehicle(){}
    virtual ~IVehicle(){}
    virtual void run() = 0;
};

class Car : public IVehicle{
public:
    Car(){}
    ~Car(){}
    void run() override{
        std::cout << "My Car is running \n";
    }
};

class Motorbike : public IVehicle{
public:
    Motorbike(){}
    ~Motorbike(){}
    void run() override{
        std::cout << "My Motorbike is running \n";
    }
};

class Bicycle : public IVehicle{
public:
    Bicycle(){}
    ~Bicycle(){}
    void run() override{
        std::cout << "My Bicycle is running \n";
    }
};

Có nhiều cách để để design Factory Method nhưng cách chủ yếu và đơn giản mà mọi người hay dùng đó là static method như sau:

class VehicleFactory
{
public:
    static std::shared_ptr<IVehicle> createVehicle(const VehicleType& type){
        std::shared_ptr<IVehicle> vehicle = nullptr;
        switch (type)
        {
        case VehicleType::CAR:
            vehicle = std::make_shared<Car>();

            break;
        case VehicleType::MOTORBIKE:
            vehicle = std::make_shared<Motorbike>();
            break;
        case VehicleType::BICYLE:
            vehicle = std::make_shared<Bicycle>();
            break;  
        default:
            break;
        }
        return vehicle;
    }
};

Và khi sử dụng rất đơn giản:

int main(){
    VehicleType type = VehicleType::CAR;
    auto vehicle = VehicleFactory::createVehicle(type);
    vehicle->run();
    return 0;
}

Như các bạn thấy, thay vì mỗi lần chọn xe bạn sẽ cần suy nghĩ lại đoạn code if else ở đầu quá nhiều, thì giờ chỉ cần chọn loại xe theo sở thích của bạn gái bằng đúng 1 câu lệnh. Điều này sẽ giúp code gọn gàng, dễ maintain, phát triển hơn khi sau này có thêm nhiều objects phức tạp hơn.

Ứng dụng

Factory Method được ứng khi:

  • Có rất nhiều objects (superclass) được implement từ một interface, cần đẩy việc khởi tạo các class đó ra một subclass riêng giúp cho việc sử dụng dễ dàng hơn, như ở ví dụ trên sẽ có rất nhiều loại phương tiện sẽ được thêm mới nên cần áp sụng Factory Method để dễ maintain sau này.
  • Muốn che lấp đi sự phức tạp của việc tạo objects, và tạo một interface dễ sử dụng hơn cho người dùng. Như ở ví dụ trên việc khởi tạo của các phương tiện khá đơn giản, nhưng hãy thử tưởng tượng nếu các Objects đó có rất nhiều tham số khi khởi tạo, đó người dùng sẽ không thể nhớ hết được các tham số cho mỗi loại Object họ muốn dùng, thay vào đó chúng ta sẽ tham số hóa tạo Factory Method vạo tạo một interface đơn giản hơn cho tất cả.

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

Ưu điểm

  • Tạo một interface dùng chung dễ dàng sử dụng cho việc khởi tạo các objects khác nhau
  • Che giấu sự phức tạp của việc khởi tạo của các subclass
  • Đảm bảo tính Open-Closed mỗi khi có thêm các đối tượng mới mà không ảnh hưởng đến modules khác
  • Tránh được lặp lại common code (Don’t repeat yourself)
  • Hạn chế sự dàng buộc giữa client code và các subclass

Nhược điểm

  • Đôi khi khiến code phức tạo hơn trong trường hợp implement theo hướng tạo ra nhiều các subclass từ một factory method interface
  • Khó khăn trong việc refactoring một hệ thống có sẵn, tham số hóa instantiation và có thể ảnh hưởng đến client code.

Kết luận

Như vậy ở bài viết này, mình đã giới thiệu về một trong hai loại Factory Patterns là Factory Method. 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