Menu
CoddyTech

Factory và Abstract Factory

Thuộc phần Lập trình hướng đối tượng trong hành trình C++ của Coddy. Bài học 92 trên 104.

Mẫu Factory ủy quyền việc tạo đối tượng cho một phương thức hoặc lớp riêng, giúp bạn tạo đối tượng mà không cần chỉ định lớp cụ thể của chúng. Mẫu này hữu ích khi logic tạo đối tượng phức tạp hoặc khi bạn muốn tách rời code phía máy khách khỏi các cách triển khai cụ thể.

Factory đơn giản dùng một phương thức để trả về các kiểu dẫn xuất khác nhau dựa trên đầu vào:

#include <iostream>
#include <memory>

class Button {
public:
    virtual void render() = 0;
    virtual ~Button() = default;
};

class WindowsButton : public Button {
public:
    void render() override { std::cout << "Windows button\n"; }
};

class MacButton : public Button {
public:
    void render() override { std::cout << "Mac button\n"; }
};

// Hàm (function) tạo đối tượng
std::unique_ptr<Button> createButton(const std::string& os) {
    if (os == "windows") return std::make_unique<WindowsButton>();
    if (os == "mac") return std::make_unique<MacButton>();
    return nullptr;
}

Abstract Factory tiến thêm một bước bằng cách tạo ra các họ đối tượng có liên quan. Thay vì dùng một phương thức nhà máy, bạn có một giao diện nhà máy với nhiều phương thức tạo đối tượng:

class Checkbox {
public:
    virtual void check() = 0;
    virtual ~Checkbox() = default;
};

class WindowsCheckbox : public Checkbox {
public:
    void check() override { std::cout << "Windows checkbox\n"; }
};

class MacCheckbox : public Checkbox {
public:
    void check() override { std::cout << "Mac checkbox\n"; }
};

// Nhà máy trừu tượng
class GUIFactory {
public:
    virtual std::unique_ptr<Button> createButton() = 0;
    virtual std::unique_ptr<Checkbox> createCheckbox() = 0;
    virtual ~GUIFactory() = default;
};

class WindowsFactory : public GUIFactory {
public:
    std::unique_ptr<Button> createButton() override {
        return std::make_unique<WindowsButton>();
    }
    std::unique_ptr<Checkbox> createCheckbox() override {
        return std::make_unique<WindowsCheckbox>();
    }
};

Dùng Factory khi bạn cần tạo đối tượng linh hoạt, và dùng Abstract Factory khi bạn cần đảm bảo các đối tượng liên quan hoạt động nhất quán với nhau.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống Tạo tài liệu bằng cách sử dụng mẫu Factory và Abstract Factory. Bạn sẽ tạo một hệ thống có thể tạo nhiều loại tài liệu khác nhau (báo cáo và hóa đơn) với các định dạng đầu ra khác nhau (PDF và HTML), qua đó cho thấy cách các factory tách biệt việc tạo đối tượng khỏi code phía client.

Bạn sẽ sắp xếp code trong bốn tệp:

  • Document.h: Định nghĩa hệ thống phân cấp sản phẩm tài liệu.

    Tạo một lớp cơ sở trừu tượng Document với phương thức ảo thuần túy generate() trả về một chuỗi mô tả tài liệu được tạo, cùng với hàm hủy ảo.

    Sau đó, tạo bốn lớp tài liệu cụ thể:

    • PDFReport: generate() trả về "Generating PDF Report"
    • HTMLReport: generate() trả về "Generating HTML Report"
    • PDFInvoice: generate() trả về "Generating PDF Invoice"
    • HTMLInvoice: generate() trả về "Generating HTML Invoice"
  • DocumentFactory.h: Cài đặt cả hàm factory đơn giản và abstract factory.

    Trước tiên, tạo một hàm factory đơn giản có tên createDocument, nhận hai chuỗi: loại tài liệu ("report" hoặc "invoice") và định dạng ("pdf" hoặc "html"). Hàm này sẽ trả về một std::unique_ptr<Document> trỏ đến tài liệu cụ thể phù hợp. Trả về nullptr nếu tổ hợp không hợp lệ.

    Sau đó, tạo một lớp abstract factory có tên DocumentFactory với hai phương thức ảo thuần túy:

    • createReport(): trả về một std::unique_ptr<Document>
    • createInvoice(): trả về một std::unique_ptr<Document>

    Cài đặt hai factory cụ thể:

    • PDFFactory: tạo PDFReport và PDFInvoice
    • HTMLFactory: tạo HTMLReport và HTMLInvoice
  • DocumentService.h: Tạo một dịch vụ sử dụng abstract factory.

    Định nghĩa lớp DocumentService nhận một std::unique_ptr<DocumentFactory> trong hàm khởi tạo. Lớp này cần có phương thức tên là generateAllDocuments(), sử dụng factory để tạo cả báo cáo lẫn hóa đơn, sau đó in kết quả gọi generate() trên từng tài liệu, mỗi kết quả trên một dòng.

  • main.cpp: Minh họa cả hai cách dùng factory.

    Đọc hai đầu vào:

    1. Loại tài liệu (report hoặc invoice)
    2. Định dạng (pdf hoặc html)

    Trước tiên, dùng hàm factory đơn giản để tạo tài liệu dựa trên các đầu vào. In kết quả gọi generate() trên tài liệu đó.

    Sau đó, in một dòng trống.

    Tiếp theo, tạo một DocumentService với factory phù hợp dựa trên đầu vào định dạng (dùng PDFFactory cho pdf, HTMLFactory cho html). Gọi generateAllDocuments() để cho thấy abstract factory tạo ra một họ tài liệu có liên quan như thế nào.

Ví dụ, với đầu vào report và pdf:

Generating PDF Report

Generating PDF Report
Generating PDF Invoice

Với đầu vào invoice và html:

Generating HTML Invoice

Generating HTML Report
Generating HTML Invoice

Hãy chú ý cách factory đơn giản tạo một tài liệu dựa trên cả hai tham số, trong khi abstract factory đảm bảo mọi tài liệu do một dịch vụ tạo ra đều dùng cùng một định dạng. Đây là lợi ích chính của Abstract Factory khi bạn cần các họ đối tượng có liên quan hoạt động nhất quán với nhau.

Tự làm thử

#include <iostream>
#include <string>
#include <memory>
#include "Document.h"
#include "DocumentFactory.h"
#include "DocumentService.h"

using namespace std;

int main() {
    // Đọc các giá trị đầu vào
    string documentType;
    string format;
    cin >> documentType;
    cin >> format;
    
    // TODO: Dùng hàm (function) factory đơn giản để tạo một tài liệu
    // dựa trên các giá trị đầu vào documentType và format
    // In ra kết quả khi gọi generate() trên tài liệu đó
    
    // In ra một dòng trống
    cout << endl;
    
    // TODO: Tạo DocumentService với factory phù hợp
    // dựa trên giá trị đầu vào format (PDFFactory cho "pdf", HTMLFactory cho "html")
    // Gọi generateAllDocuments() để minh họa factory trừu tượng
    
    return 0;
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch C++ online