빌더는 복잡한 객체들을 단계별로 생성할 수 있도록 하는 생성 디자인 패턴입니다. 이 패턴을 사용하면 같은 제작 코드를 사용하여 객체의 다양한 유형들과 표현을 제작할 수 있습니다.

문제
많은 필드와 중첩된 객체들을 힘들게 단계별로 초기화해야 하는 복잡한 객체를 상상해 보세요. 이러한 초기화 코드는 일반적으로 많은 매개변수가 있는 괴물 같은 생성자 내부에 묻혀 있습니다. 또, 더 최악의 상황에는 클라이언트 코드 전체에 흩어져 있을 수도 있습니다.

당신은 객체의 가능한 모든 설정에 자식 클래스를 만들어 프로그램을 매우 복잡하게 만들 수 있습니다.
예를 들어 House(집) 객체를 만드는 방법에 대해 생각해 봅시다. 간단한 집을 지으려면 네 개의 벽과 바닥을 만든 후 문도 설치하고 한 쌍의 창문도 맞춘 후 지붕도 만들어야 합니다. 하지만 뒤뜰과 기타 물품(난방 시스템, 배관 및 전기 배선 등)이 있는 더 크고 현대적인 집을 원하면 어떻게 해야 할까요?
위 문제의 가장 간단한 해결책은 기초 House 클래스를 확장하고 매개변수의 모든 조합을 포함하는 자식 클래스들의 집합을 만드는 것입니다. 그러나 당신은 결국 상당한 수의 자식 클래스를 만들게 될 것입니다. 새로운 매개변수(예: 현관 스타일)를 추가할 때마다 이 계층구조는 훨씬 더 복잡해질 것입니다.
자식 클래스들을 늘리지 않는 다른 접근 방식이 있습니다. 기초 House 클래스에 House 객체를 제어하는 모든 가능한 매개변수를 포함한 거대한 생성자를 만드는 것입니다. 이 접근 방식은 실제로 자식 클래스들의 필요성을 제거하나, 다른 문제를 만들어 냅니다.

매개변수가 많은 생성자의 단점은 모든 매개변수가 항상 필요한 것은 아니라는 점입니다.
보통 대부분의 매개변수가 사용되지 않아 생성자 호출들의 코드가 매우 못생겨질 것입니다. 예를 들어, 극소수의 집들에만 수영장이 있으므로 수영장과 관련된 매개변수들은 십중팔구 사용되지 않을 것입니다.
해결책
빌더 패턴은 자신의 클래스에서 객체 생성 코드를 추출하여 builders(건축업자들)라는 별도의 객체들로 이동하도록 제안합니다.

빌더 패턴은 복잡한 객체들을 단계별로 생성할 수 있도록 합니다. 빌더는 제품이 생성되는 동안 다른 객체들이 제품에 접근(access)하는 것을 허용하지 않습니다.
이 패턴은 객체 생성을 일련의 단계들(buildWalls(벽 건설), buildDoor(문 건설) 등)로 정리하며, 객체를 생성하고 싶으면 위 단계들을 builder(빌더) 객체에 실행하면 됩니다. 또 중요한 점은 모든 단계를 호출할 필요가 없다는 것으로, 객체의 특정 설정을 제작하는 데 필요한 단계들만 호출하면 됩니다.
일부 건축 단계들은 제품의 다양한 표현을 건축해야 하는 경우 다른 구현들이 필요할 수 있습니다. 예를 들어, 오두막의 벽은 나무로 지을 수 있지만 성벽은 돌로 지어야 합니다.
이런 경우 같은 건축 단계들의 집합을 다른 방식으로 구현하는 여러 다른 빌더 클래스를 생성할 수 있으며, 그런 다음 건축 프로세스(즉, 건축 단계에 대한 순서화된 호출들의 집합)내에서 이러한 빌더들을 사용하여 다양한 종류의 객체를 생성할 수 있습니다.

다양한 빌더들은 다양한 방식으로 같은 작업을 실행합니다.
예를 들어, 나무와 유리로 모든 것을 건축하는 건축가, 돌과 철로 모든 것을 건축하는 두 번째 건축가, 금과 다이아몬드로 모든 것을 건축하는 세 번째 건축가가 있다고 상상해 보세요. 이 세 건축가에 대해 같은 단계들의 집합을 호출하면 첫 번째 건축업자에게서부터는 일반 주택을, 두 번째 건축업자에게서부터는 작은 성을, 세 번째 건축업자에게서부터는 궁전을 얻습니다. 그러나 위에 예시된 경우는 건축 단계들을 호출하는 클라이언트 코드가 공통 인터페이스를 사용하여 빌더들과 상호 작용할 수 있는 경우에만 작동합니다.
디렉터 (관리자)
더 나아가 제품을 생성하는 데 사용하는 빌더 단계들에 대한 일련의 호출을 디렉터 (관리자)라는 별도의 클래스로 추출할 수 있습니다. 디렉터 클래스는 제작 단계들을 실행하는 순서를 정의하는 반면 빌더는 이러한 단계들에 대한 구현을 제공합니다.

디렉터는 작동하는 제품을 얻기 위하여 어떤 건축 단계들을 실행해야 하는지 알고 있습니다.
프로그램에 디렉터 클래스를 포함하는 것은 필수사항은 아닙니다. 당신은 언제든지 클라이언트 코드에서 생성 단계들을 직접 특정 순서로 호출할 수 있습니다. 그러나 디렉터 클래스는 다양한 생성 루틴들을 배치하여 프로그램 전체에서 재사용할 수 있는 좋은 장소가 될 수 있습니다.
또한 디렉터 클래스는 클라이언트 코드에서 제품 생성의 세부 정보를 완전히 숨깁니다. 클라이언트는 빌더를 디렉터와 연관시키고 디렉터와 생성을 시행한 후 빌더로부터 결과를 얻기만 하면 됩니다.
class Product1{
public:
std::vector<std::string> parts_;
void ListParts()const{
std::cout << "Product parts: ";
for (size_t i=0;i<parts_.size();i++){
if(parts_[i]== parts_.back()){
std::cout << parts_[i];
}else{
std::cout << parts_[i] << ", ";
}
}
std::cout << "\n\n";
}
};
class Builder{
public:
virtual ~Builder(){}
virtual void ProducePartA() const =0;
virtual void ProducePartB() const =0;
virtual void ProducePartC() const =0;
};
class ConcreteBuilder1 : public Builder{
private:
Product1* product;
/**
* A fresh builder instance should contain a blank product object, which is
* used in further assembly.
*/
public:
ConcreteBuilder1(){
this->Reset();
}
~ConcreteBuilder1(){
delete product;
}
void Reset(){
this->product= new Product1();
}
/**
* All production steps work with the same product instance.
*/
void ProducePartA()const override{
this->product->parts_.push_back("PartA1");
}
void ProducePartB()const override{
this->product->parts_.push_back("PartB1");
}
void ProducePartC()const override{
this->product->parts_.push_back("PartC1");
}
Product1* GetProduct() {
Product1* result= this->product;
this->Reset();
return result;
}
};
class Director{
private:
Builder* builder;
public:
void set_builder(Builder* builder){
this->builder=builder;
}
void BuildMinimalViableProduct(){
this->builder->ProducePartA();
}
void BuildFullFeaturedProduct(){
this->builder->ProducePartA();
this->builder->ProducePartB();
this->builder->ProducePartC();
}
};
void ClientCode(Director& director)
{
ConcreteBuilder1* builder = new ConcreteBuilder1();
director.set_builder(builder);
std::cout << "Standard basic product:\n";
director.BuildMinimalViableProduct();
Product1* p= builder->GetProduct();
p->ListParts();
delete p;
std::cout << "Standard full featured product:\n";
director.BuildFullFeaturedProduct();
p= builder->GetProduct();
p->ListParts();
delete p;
// Remember, the Builder pattern can be used without a Director class.
std::cout << "Custom product:\n";
builder->ProducePartA();
builder->ProducePartC();
p=builder->GetProduct();
p->ListParts();
delete p;
delete builder;
}
int main(){
Director* director= new Director();
ClientCode(*director);
delete director;
return 0;
}
'디자인 패턴' 카테고리의 다른 글
| Singleton 패턴 (0) | 2024.06.26 |
|---|---|
| Prototype 패턴 (0) | 2024.06.26 |
| Abstarct Factory 패턴 (0) | 2024.06.13 |
| Factory 패턴 (0) | 2024.06.13 |
| SOLID 원칙 (0) | 2024.04.28 |