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.
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
Documentvới phương thức ảo thuần túygenerate()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ộtstd::unique_ptr<Document>trỏ đến tài liệu cụ thể phù hợp. Trả vềnullptrnếu tổ hợp không hợp lệ.Sau đó, tạo một lớp abstract factory có tên
DocumentFactoryvới hai phương thức ảo thuần túy:createReport(): trả về mộtstd::unique_ptr<Document>createInvoice(): trả về mộtstd::unique_ptr<Document>
Cài đặt hai factory cụ thể:
PDFFactory: tạoPDFReportvàPDFInvoiceHTMLFactory: tạoHTMLReportvàHTMLInvoice
DocumentService.h: Tạo một dịch vụ sử dụng abstract factory.Định nghĩa lớp
DocumentServicenhận mộtstd::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ọigenerate()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:
- Loại tài liệu (
reporthoặcinvoice) - Định dạng (
pdfhoặchtml)
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
DocumentServicevới factory phù hợp dựa trên đầu vào định dạng (dùngPDFFactorychopdf,HTMLFactorychohtml). GọigenerateAllDocuments()để cho thấy abstract factory tạo ra một họ tài liệu có liên quan như thế nào.- Loại tài liệu (
Ví dụ, với đầu vào report và pdf:
Generating PDF Report
Generating PDF Report
Generating PDF InvoiceVới đầu vào invoice và html:
Generating HTML Invoice
Generating HTML Report
Generating HTML InvoiceHã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;
}
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
1Nền tảng về OOP
Tệp bên ngoàiBiên dịch và tạo bản dựng C++Tệp header và tệp nguồnNamespace và phạm viGiới thiệu về OOP trong C++Lớp và đối tượngCon trỏ thisPhương thức (hàm thành viên)Thuộc tính (dữ liệu thành viên)Cơ bản về hàm khởi tạo và hàm hủyÔn tập: máy tính đơn giản4Thuộc tính của lớp
Thành viên đối tượng và thành viên tĩnhHàm truy xuất và hàm thiết lậpHàm thành viên hằngTừ khóa mutablePhương thức và biến tĩnhHàm và lớp bạnÔn tập: Trình quản lý tài khoản ngân hàng7Kế thừa
Kế thừa cơ bảnMức truy cập trong kế thừaThứ tự gọi hàm khởi tạo và hàm hủyGhi đè phương thứcHàm ảo và VTableĐa kế thừaKế thừa ảoÔn tập: hệ phân cấp nhân viên10Tổng quan về STL
Tổng quan và triết lý của STLCác vùng chứa của STLBộ lặpCác thuật toán của STLHàm tử và biểu thức lambdaÔn tập: tần suất từ13Mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếMẫu SingletonFactory và Abstract FactoryMẫu BuilderMẫu ObserverMẫu Strategy2Quản lý bộ nhớ
Bộ nhớ ngăn xếp và heapCon trỏ và tham chiếuBộ nhớ động (new/delete)Con trỏ thông minh trong C++RAII trong C++Ôn tập: Trình quản lý mảng động5Tính đóng gói
Phạm vi truy cập trong C++Tìm hiểu sâu về phạm vi truy cậpẨn thông tinStruct và lớpLớp lồng nhau và lớp bên trongÔn tập: hệ thống hồ sơ sinh viên8Đa hình
Đa hình lúc biên dịch và khi chạyNạp chồng hàmÔn lại hàm ảoHàm ảo thuần túyLớp trừu tượngThiết kế interface trong C++Ép kiểu động và RTTIÔn tập: Máy tính hình học3Hàm khởi tạo và hàm hủy
Hàm khởi tạo mặc địnhHàm khởi tạo có tham sốHàm khởi tạo sao chépHàm khởi tạo di chuyểnDanh sách khởi tạoHàm khởi tạo ủy quyềnTìm hiểu sâu về hàm hủyQuy tắc 3 / 5 / 0Ôn tập: lớp chuỗi6Nạp chồng toán tử
Giới thiệu về nạp chồng toán tửNạp chồng toán tử số họcNạp chồng toán tử so sánhToán tử luồngNạp chồng toán tử gánNạp chồng toán tử [] và ()Toán tử chuyển đổi kiểuÔn tập: lớp ma trận9Mẫu
Mẫu hàmMẫu lớpChuyên biệt hóa mẫuMẫu biến tham sốCơ bản về SFINAE và đặc điểm kiểuÔn tập: vùng chứa tổng quátTự luyện tập: Trình biên dịch C++ online