Builder is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.

Problem
Một căn nhà cần phải có tường, mái nhà và cánh cửa:
class House {
private:
Wall _wall;
Roof _roof;
Floor _floor;
public:
House(Wall wall, Roof roof, Floor floor);
};Có thể thấy, constructor của căn nhà chứa rất nhiều tham số và có thể phình to ra hơn nữa trong tương lai. Trong trường hợp ta muốn xây dựng một căn nhà có cả cây và tượng trang trí, ta cần phải tạo ra một constructor khác có nhiều tham số hơn:
class House {
//...
public:
House(Wall wall, Roof roof, Floor floor);
House(Wall wall, Roof roof, Floor floor, Tree tree, Statue statue);
};Điều này tạo ra một vấn đề gọi là telescoping constructor (các constructor cứ nối nhau kéo dài ra, trông giống như một cái ống nhòm). Việc có quá nhiều constructor làm cho chúng ta bối rối khi chọn constructor giữa một rừng constructor tương tự nhau.
Chúng ta có thể rút gọn lại thành một constructor duy nhất và truyền NULL vào các tham số mà ta không muốn sử dụng:
class House {
// ...
public:
// Super Ultimate Constructor
House(Wall wall, Roof roof, Floor floor, Tree tree, Statue statue, ...);
};Tuy nhiên, cách làm này có thể gây ra việc nhầm lẫn thứ tự tham số cũng như là làm lãng phí các tham số không sử dụng.

Chúng ta có thể giải quyết vấn đề trên bằng cách tạo ra các lớp dẫn xuất cho từng loại căn nhà:
class House {
// ...
public:
House(Wall wall, Roof roof, Floor floor);
};
class WibuHouse : public House {
// ...
public:
House(Wall wall, Roof roof, Floor floor, Tree sakuraTree, Statue waifuStatue);
};
class StreamerHouse : public House {
// ...
public:
House(Wall wall, Roof roof, Floor floor, SpecialRoom gamingRoom, Studio studio);
};Đến một lúc nào đó, số lượng các lớp dẫn xuất sẽ trở nên rất nhiều và khó kiểm soát.
Solution
Để giải quyết tình trạng trên, ta có thể tách các đoạn code khởi tạo object ra một builder class và chia việc khởi tạo thành nhiều bước chẳng hạn như buildWalls hay buildDoor.
Khi cần tạo ra một object, ta sẽ thực thi một chuỗi liên tiếp các bước đã được định nghĩa. Điều quan trọng là ta không nhất thiết phải gọi thực thi tất cả các bước.

Có thể một đối tượng có nhiều dạng biểu diễn khác nhau. Ví dụ, lâu đài thì xây tường bằng đá còn cabin thì xây tường bằng gỗ. Trong trường hợp đó, ta cần tạo ra hai builder class có cùng tập các bước để xây dựng một object nhưng mỗi bước ở từng class sẽ không giống nhau.

Chúng ta cũng có thể xây dựng một director class giúp gọi thực thi các phương thức của builder để tạo ra một object cụ thể nào đó. Với cách này, director class còn giúp che giấu những đoạn code khởi tạo khỏi client.
Structure
Builder pattern sẽ bao gồm những thành phần sau:
- Builder: là một interface khai báo những bước cần thiết để tạo ra object.
- Concrete builder: lưu một tham chiếu đến product và cung cấp những implementation khác nhau của các bước khởi tạo object.
- Product: là các lớp phức tạp được tạo ra từ builder.
- Director: lưu một tham chiếu đến builder và định nghĩa thứ tự gọi thực hiện các bước khởi tạo object.
- Client cần phải liên kết ít nhất một builder object với director thông qua constructor của director.
Minh họa:

Implementation
Giả sử ta cần triển khai builder pattern để tạo ra các đối tượng máy tính. Mỗi máy tính cần phải có tối thiểu 1 CPU, 1 thanh RAM và 1 HDD mới có thể hoạt động. Ngoài ra, máy tính còn có thể có thêm VGA và cooler.
Product
Sản phẩm cuối cùng sẽ là một đối tượng thuộc lớp đối tượng sau:
class Computer {
private:
std::vector<std::string> _parts;
public:
void addPart(std::string part) {
_parts.push_back(part);
}
void listParts() {
std::cout << "Product parts: ";
for (std::string part : _parts) {
if (part == _parts.back()) {
std::cout << part;
}
else {
std::cout << part << ", ";
}
}
std::cout << std::endl;
}
};Có thể thấy, các linh kiện máy tính sẽ được lưu ở trong vector _parts. Lớp Computer còn có phương thức addPart giúp thêm vào một linh kiện và phương thức listParts giúp liệt kê các linh kiện.
Builder
Xây dựng builder interface như sau:
class Builder {
public:
virtual void produceCpu() = 0;
virtual void produceRam() = 0;
virtual void produceHdd() = 0;
virtual void produceVga() = 0;
virtual void produceCooler() = 0;
};Concrete Builder
Tạo ra concrete builder để lắp ráp một chiếc máy tính:
class ComputerBuilder : public Builder {
private:
Computer* _computer = nullptr;
public:
ComputerBuilder() {
reset();
}
void reset() {
_computer = new Computer();
}
public:
void produceCpu() { _computer->addPart("CPU"); }
void produceRam() { _computer->addPart("RAM"); }
void produceHdd() { _computer->addPart("HDD"); }
void produceVga() { _computer->addPart("VGA"); }
void produceCooler() { _computer->addPart("Cooler"); }
Computer* getComputer() {
Computer* result = _computer;
reset();
return result;
}
};Có thể thấy, concrete builder:
- Lưu một tham chiếu đến sản phẩm cuối cùng.
- Có phương thức
resetgiúp khởi tạo vùng nhớ cho tham chiếu. Constructor của concrete builder sẽ gọi sử dụng phương thức này. - Implement các phương thức của interface dùng để xây dựng sản phẩm cuối cùng.
- Có phương thức
getComputertrả về tham chiếu đến sản phẩm cuối cùng và đồng thời gọi thực hiệnresetđể cấp phát một vùng nhớ mới.
Director
Xây dựng director có chứa một tham chiếu đến concrete builder như sau:
class ComputerDirector {
private:
Builder* _builder = nullptr;
public:
void setBuilder(Builder* builder) {
_builder = builder;
}
void buildMinimalViableComputer() {
_builder->produceCpu();
_builder->produceRam();
_builder->produceHdd();
}
void buildFullFeaturedComputer() {
_builder->produceCpu();
_builder->produceRam();
_builder->produceHdd();
_builder->produceVga();
_builder->produceCooler();
}
};Có hai option khi tạo ra một máy tính:
- Minimal viable: bao gồm 1 CPU, 1 RAM và 1 HDD. Option này tương ứng với phương thức
buildMinimalViableComputer. - Full-featured: có thêm VGA và cooler. Option này tương ứng với phương thức
buildFullFeaturedComputer.
Usage
Gọi sử dụng như sau:
void TestBuilder() {
ComputerDirector* computerDirector = new ComputerDirector();
ComputerBuilder* computerBuilder = new ComputerBuilder();
computerDirector->setBuilder(computerBuilder);
std::cout << "Standard basic product:\n";
computerDirector->buildMinimalViableComputer();
Computer* minimalViableComputer = computerBuilder->getComputer();
minimalViableComputer->listParts();
delete minimalViableComputer;
std::cout << "Standard full featured product:\n";
computerDirector->buildFullFeaturedComputer();
Computer* fullFeaturedComputer = computerBuilder->getComputer();
fullFeaturedComputer->listParts();
delete fullFeaturedComputer;
}Output:
Standard basic product:
Product parts: CPU, RAM, HDD
Standard full featured product:
Product parts: CPU, RAM, HDD, VGA, CoolerApplicability
Sử dụng builder pattern khi cần:
- Loại bỏ “telescopic constructor”.
- Tạo ra nhiều thể hiện khác nhau của đối tượng.
Pros and Cons
| Pros | Cons |
|---|---|
| Chúng ta có thể xây dựng đối tượng theo từng bước | Code phức tạp hơn |
| Có thể sử dụng lại các bước khi xây dựng những thể hiện khác nhau của đối tượng | |
| Single responsibility: tách biệt đoạn code khởi tạo phức tạp ra khỏi các đoạn code logic |