Mẫu Builder
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 93 trên 104.
Mẫu Builder tách biệt quá trình xây dựng một đối tượng phức tạp khỏi cách biểu diễn đối tượng đó, giúp bạn tạo các cấu hình khác nhau theo từng bước. Cách này đặc biệt hữu ích khi một đối tượng có nhiều tham số tùy chọn hoặc cần một trình tự xây dựng cụ thể.
Thay vì dùng hàm khởi tạo có nhiều tham số (dễ gây nhầm lẫn), Builder cung cấp các phương thức có tên rõ ràng cho từng tùy chọn cấu hình:
#include <iostream>
#include <string>
class Pizza {
public:
std::string dough;
std::string sauce;
std::string topping;
bool cheese;
void describe() const {
std::cout << dough << " dough, " << sauce << " sauce, "
<< topping << (cheese ? ", with cheese" : "") << "\n";
}
};
class PizzaBuilder {
private:
Pizza pizza;
public:
PizzaBuilder& setDough(const std::string& d) {
pizza.dough = d;
return *this;
}
PizzaBuilder& setSauce(const std::string& s) {
pizza.sauce = s;
return *this;
}
PizzaBuilder& setTopping(const std::string& t) {
pizza.topping = t;
return *this;
}
PizzaBuilder& addCheese() {
pizza.cheese = true;
return *this;
}
Pizza build() { return pizza; }
};
int main() {
Pizza margherita = PizzaBuilder()
.setDough("thin")
.setSauce("tomato")
.setTopping("basil")
.addCheese()
.build();
margherita.describe();
}Mỗi phương thức setter trả về tham chiếu đến builder (return *this), cho phép nối chuỗi phương thức để tạo giao diện linh hoạt. Phương thức build() hoàn tất và trả về đối tượng đã được tạo.
Dùng Builder khi tạo đối tượng có nhiều thành phần tùy chọn, khi bạn muốn code khởi tạo dễ đọc hoặc khi cùng một quy trình khởi tạo cần tạo ra nhiều cách biểu diễn khác nhau.
Thử thách
DễHãy xây dựng hệ thống Computer Builder cho phép người dùng từng bước cấu hình máy tính cá nhân theo nhu cầu. Đây là tình huống lý tưởng để áp dụng mẫu Builder: máy tính có nhiều linh kiện tùy chọn, và chúng ta muốn có cách rõ ràng, dễ đọc để lắp ráp các cấu hình khác nhau.
Bạn sẽ sắp xếp code trong ba tệp:
Computer.h: Định nghĩa lớp sản phẩm đại diện cho một máy tính đã được cấu hình đầy đủ.Lớp
Computercủa bạn cần lưu các linh kiện sau dưới dạng thành viên private:cpu(chuỗi),ram(chuỗi),storage(chuỗi),gpu(chuỗi) vàhasWifi(giá trị boolean). Mặc định, khởi tạohasWifithànhfalsevà các chuỗi thành rỗng.Thêm phương thức public có tên
showSpecs()để hiển thị cấu hình máy tính. Với mỗi linh kiện đã được thiết lập (chuỗi không rỗng), hãy in linh kiện đó trên một dòng riêng. Chỉ in khả năng kết nối wifi khi tính năng này được bật.ComputerBuilder.h: Tạo lớp builder để xây dựng máy tính bằng giao diện thông suốt.Lớp
ComputerBuildercần có một thành viênComputerprivate để dần hoàn thiện máy tính. Triển khai các phương thức sau, mỗi phương thức trả về một tham chiếu đến builder để cho phép gọi nối tiếp các phương thức:setCPU(const std::string& cpu)setRAM(const std::string& ram)setStorage(const std::string& storage)setGPU(const std::string& gpu)addWifi(): bật khả năng kết nối wifi
Đồng thời, triển khai phương thức
build()để trả về đối tượngComputerđã hoàn thiện và đặt lại builder để có thể sử dụng lại.main.cpp: Kết hợp mọi thứ để xây dựng một máy tính theo cấu hình tùy chỉnh.Đọc bốn đầu vào:
- Mẫu CPU (chuỗi)
- Thông số RAM (chuỗi)
- Thông số bộ lưu trữ (chuỗi)
- Có thêm wifi hay không:
yeshoặcno
Dùng
ComputerBuilderđể xây dựng máy tính với CPU, RAM và bộ lưu trữ đã nhập. Nếu đầu vào wifi làyes, hãy gọi thêmaddWifi(). Lưu ý rằng cấu hình này không chỉ định GPU: builder cần xử lý linh kiện tùy chọn một cách phù hợp.Sau khi xây dựng xong, gọi
showSpecs()trên máy tính vừa tạo để hiển thị cấu hình.
Phương thức showSpecs() cần xuất kết quả theo định dạng sau, chỉ hiển thị các linh kiện đã được thiết lập:
CPU: [value]
RAM: [value]
Storage: [value]
GPU: [value]
Wifi: EnabledVí dụ, với đầu vào Intel i7-12700K, 32GB DDR5, 1TB NVMe SSD và yes:
CPU: Intel i7-12700K
RAM: 32GB DDR5
Storage: 1TB NVMe SSD
Wifi: EnabledVới đầu vào AMD Ryzen 5 5600X, 16GB DDR4, 512GB SSD và no:
CPU: AMD Ryzen 5 5600X
RAM: 16GB DDR4
Storage: 512GB SSDHãy chú ý cách mẫu Builder giúp quá trình xây dựng dễ đọc và linh hoạt. Bạn có thể dễ dàng thêm hoặc bỏ qua linh kiện, còn việc gọi nối tiếp các phương thức tạo nên đoạn code rõ ràng, dễ hiểu. Dòng GPU không xuất hiện vì chúng ta chưa thiết lập GPU, cho thấy builder xử lý linh kiện tùy chọn một cách tự nhiên.
Tự làm thử
#include <iostream>
#include <string>
#include "ComputerBuilder.h"
using namespace std;
int main() {
// Đọc các giá trị đầu vào
string cpu, ram, storage, wifiChoice;
getline(cin, cpu);
getline(cin, ram);
getline(cin, storage);
getline(cin, wifiChoice);
// TODO: Tạo một instance của ComputerBuilder
// TODO: Gọi nối tiếp các phương thức để thiết lập CPU, RAM và bộ nhớ lưu trữ
// TODO: Nếu wifiChoice là "yes", gọi thêm addWifi()
// TODO: Gọi build() để lấy đối tượng Computer
// TODO: Gọi showSpecs() trên máy tính đã tạo để hiển thị cấu hình
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