빌더 패턴(Builder Pattern)은 복잡한 객체를 단계별로 생성할 수 있도록 도와주는 디자인 패턴입니다. 이 패턴은 객체 생성의 과정을 캡슐화하여, 동일한 생성 절차에서 서로 다른 표현을 만들 수 있게 해줍니다. 특히, 객체의 속성이 많거나 복잡한 경우에 유용합니다.

주요 특징
단계별 객체 생성: 객체를 생성할 때 여러 단계로 나누어 생성할 수 있어, 각 단계를 통해 필요한 속성을 설정할 수 있습니다.
불변성: 생성된 객체는 불변(immutable)으로 만들 수 있어, 생성 후 상태 변경을 방지할 수 있습니다.
가독성 향상: 명확한 메서드 체이닝을 통해 객체 생성 과정을 쉽게 이해할 수 있습니다.
C++ 코드 예시
아래는 빌더 패턴을 사용하여 복잡한 Pizza 객체를 생성하는 예시입니다.
#include <iostream>
#include <string>
#include <vector>
// Product 클래스
class Pizza {
private:
std::string size;
std::vector<std::string> toppings;
public:
Pizza(std::string size, std::vector<std::string> toppings)
: size(size), toppings(toppings) {}
void display() {
std::cout << "Pizza Size: " << size << std::endl;
std::cout << "Toppings: ";
for (const auto& topping : toppings) {
std::cout << topping << " ";
}
std::cout << std::endl;
}
};
// Builder 클래스
class PizzaBuilder {
private:
std::string size;
std::vector<std::string> toppings;
public:
PizzaBuilder& setSize(const std::string& size) {
this->size = size;
return *this;
}
PizzaBuilder& addTopping(const std::string& topping) {
toppings.push_back(topping);
return *this;
}
Pizza build() {
return Pizza(size, toppings);
}
};
// 클라이언트 코드
int main() {
PizzaBuilder builder;
Pizza pizza = builder.setSize("Large")
.addTopping("Cheese")
.addTopping("Pepperoni")
.addTopping("Olives")
.build();
pizza.display();
return 0;
}
설명
1. Product 클래스: Pizza 클래스는 생성할 객체를 정의합니다. 피자의 사이즈와 토핑을 속성으로 가집니다.
2. Builder 클래스: PizzaBuilder 클래스는 피자를 생성하기 위한 메서드를 제공합니다. 사용자는 사이즈를 설정하고, 원하는 토핑을 추가할 수 있습니다.
3. 메서드 체이닝: setSize와 addTopping 메서드는 PizzaBuilder의 참조를 반환하므로, 메서드 체이닝을 통해 연속적으로 호출할 수 있습니다.
4. 객체 생성: 클라이언트 코드에서 PizzaBuilder를 사용하여 피자의 사이즈와 토핑을 설정하고, build 메서드를 호출하여 최종적으로 Pizza 객체를 생성합니다.
다른 예제 하나 더 살펴보겠습니다.
#include <iostream>
#include <string>
using namespace std;
// Product
class Car {
string engine;
string wheels;
string body;
public:
void setEngine(const string& e) { engine = e; }
void setWheels(const string& w) { wheels = w; }
void setBody(const string& b) { body = b; }
void showSpecifications() const {
cout << "Engine: " << engine << "\n";
cout << "Wheels: " << wheels << "\n";
cout << "Body: " << body << "\n";
}
};
// Builder
class CarBuilder {
protected:
Car* car;
public:
CarBuilder() { car = new Car(); }
virtual ~CarBuilder() { delete car; }
virtual void buildEngine() = 0;
virtual void buildWheels() = 0;
virtual void buildBody() = 0;
Car* getCar() { return car; }
};
// Concrete Builder
class SportsCarBuilder : public CarBuilder {
public:
void buildEngine() override { car->setEngine("V8 Engine"); }
void buildWheels() override { car->setWheels("18 inch Alloy Wheels"); }
void buildBody() override { car->setBody("Carbon Fiber Body"); }
};
class SUVCarBuilder : public CarBuilder {
public:
void buildEngine() override { car->setEngine("V6 Engine"); }
void buildWheels() override { car->setWheels("20 inch Steel Wheels"); }
void buildBody() override { car->setBody("High-Strength Steel Body"); }
};
// Director
class CarDirector {
CarBuilder* builder;
public:
void setBuilder(CarBuilder* b) { builder = b; }
Car* construct() {
builder->buildEngine();
builder->buildWheels();
builder->buildBody();
return builder->getCar();
}
};
// Client Code
int main() {
CarDirector director;
// Building a Sports Car
SportsCarBuilder sportsCarBuilder;
director.setBuilder(&sportsCarBuilder);
Car* sportsCar = director.construct();
cout << "Sports Car Specifications:\n";
sportsCar->showSpecifications();
// Building an SUV Car
SUVCarBuilder suvCarBuilder;
director.setBuilder(&suvCarBuilder);
Car* suvCar = director.construct();
cout << "\nSUV Car Specifications:\n";
suvCar->showSpecifications();
return 0;
}
설명
1. Product 클래스 (Car) : Car는 빌더 패턴을 통해 생성될 복잡한 객체입니다. 이 클래스는 다양한 구성 요소를 포함하며, 각 구성 요소는 단계적으로 설정됩니다.
- engine, wheels, body라는 세 가지 멤버 변수가 있으며, 각각 차량의 엔진, 바퀴, 차체를 나타냅니다.
- 각각의 setEngine, setWheels, setBody 메서드를 통해 구성 요소를 설정합니다.
- showSpecifications 메서드는 Car 객체의 현재 사양을 출력합니다.
2. 빌더 인터페이스 (CarBuilder) : CarBuilder는 객체 생성의 인터페이스를 제공합니다.
- 추상 클래스로 설계되어 있어, 구체적인 빌더 클래스들이 이를 상속받고 실제 구현을 제공합니다.
- buildEngine, buildWheels, buildBody라는 세 개의 순수 가상 메서드가 정의되어 있으며, 이는 객체 생성의 단계별 프로세스를 나타냅니다.
- getCar 메서드는 생성 중인 Car 객체를 반환합니다.
3. 구체적인 빌더 클래스 : 구체적인 빌더는 CarBuilder를 상속받아 실제 객체 생성 논리를 구현합니다.
1) SportsCarBuilder:
- 스포츠카 특성에 맞는 엔진, 바퀴, 차체를 설정합니다.
- 엔진: "V8 Engine"
- 바퀴: "18 inch Alloy Wheels"
- 차체: "Carbon Fiber Body"
2) SUVCarBuilder:
- SUV 차량 특성에 맞는 엔진, 바퀴, 차체를 설정합니다.
- 엔진: "V6 Engine"
- 바퀴: "20 inch Steel Wheels"
- 차체: "High-Strength Steel Body"
4. 디렉터 클래스 (CarDirector) : CarDirector는 빌더의 흐름을 관리하고 객체 생성 과정을 조정합니다.
- setBuilder 메서드를 통해 사용할 빌더를 설정합니다.
- construct 메서드는 빌더의 메서드들을 호출해 객체를 단계적으로 생성합니다.
- 엔진을 먼저 설정 (buildEngine).
- 그다음 바퀴를 설정 (buildWheels).
- 마지막으로 차체를 설정 (buildBody).
- 최종적으로 생성된 Car 객체를 반환합니다.
5. 클라이언트 코드 : 클라이언트는 CarDirector를 사용하여 객체를 생성하며, 빌더의 세부적인 구현을 신경 쓰지 않아도 됩니다.
- 스포츠카 생성: SportsCarBuilder를 CarDirector에 전달하여 스포츠카를 생성.
- SUV 생성: SUVCarBuilder를 CarDirector에 전달하여 SUV를 생성.
- 생성된 차량 사양을 showSpecifications로 출력.
6. 출력 결과
프로그램을 실행하면 아래와 같은 출력이 예상됩니다:
Sports Car Specifications:
Engine: V8 Engine
Wheels: 18 inch Alloy Wheels
Body: Carbon Fiber Body
SUV Car Specifications:
Engine: V6 Engine
Wheels: 20 inch Steel Wheels
Body: High-Strength Steel Body
빌더 패턴(Builder Pattern)은 객체 생성 과정에서 장점과 단점을 가지고 있습니다. 아래에서 각각을 자세히 살펴보겠습니다.
장점
1. 유연한 객체 생성: 빌더 패턴은 복잡한 객체를 단계별로 생성할 수 있게 해주므로, 고객이 원하는 다양한 조합의 객체를 쉽게 만들 수 있습니다. 각 속성을 개별적으로 설정할 수 있어 유연성이 높습니다.
2. 코드 가독성 향상: 메서드 체이닝을 사용하여 객체를 생성하는 과정이 명확해지므로, 코드가 읽기 쉬워집니다. 각 속성이 어떤 값을 가지는지 쉽게 파악할 수 있습니다.
3. 불변 객체 생성: 빌더 패턴을 사용하면 생성된 객체를 불변으로 만들 수 있어, 객체의 상태가 변경되지 않도록 보장할 수 있습니다. 이는 코드의 안정성을 높입니다.
4. 복잡한 객체 생성 간소화: 생성자가 너무 많은 매개변수를 받는 경우, 빌더 패턴을 사용하여 매개변수의 수를 줄이고 각 매개변수를 명확하게 설정할 수 있습니다.
5. 표준화된 생성 과정: 객체 생성 과정이 표준화되므로, 여러 개발자가 협업할 때 일관된 방식으로 객체를 생성할 수 있습니다. 이는 유지보수성을 높입니다.
단점
1. 복잡성 증가: 객체 생성 로직을 분리하여 빌더 클래스를 도입함으로써 코드의 복잡성이 증가할 수 있습니다. 간단한 객체의 경우에는 오히려 불필요한 복잡성을 초래할 수 있습니다.
2. 추가적인 클래스 필요: 빌더 패턴을 적용하면 추가적인 클래스(빌더 클래스)가 필요하게 되므로, 클래스의 수가 증가하고 관리해야 할 요소가 늘어납니다.
3. 객체 생성을 위한 초기화 비용: 빌더 패턴은 객체를 생성하는 데 여러 단계를 거치므로, 객체 생성 시 초기화 비용이 증가할 수 있습니다. 이는 성능에 영향을 미칠 수 있습니다.
4. 상태 관리: 빌더 패턴을 사용할 때, 객체의 상태를 관리해야 하므로, 빌더가 여러 번 사용될 때 상태를 잘 관리하지 않으면 의도치 않은 결과를 초래할 수 있습니다.
결론
빌더 패턴은 복잡한 객체를 보다 유연하고 가독성 있게 생성할 수 있는 강력한 도구입니다. 그러나 사용 시 코드의 복잡성이 증가할 수 있으므로, 간단한 객체를 생성할 때는 적합하지 않을 수 있습니다. 따라서, 객체의 복잡성과 요구 사항에 따라 빌더 패턴의 사용 여부를 신중하게 결정하는 것이 중요합니다.
'c c++ mfc' 카테고리의 다른 글
디자인패턴 - 프로토타입(Prototype) (0) | 2025.03.20 |
---|---|
디자인패턴 - 팩토리 메소드(Factory Method) (0) | 2025.03.17 |
디자인패턴 - 추상 팩토리 패턴(Abstract Factory Pattern) (0) | 2025.03.17 |
Visual Studio 재배포 패키지가 설치되지 않는 문제 해결방법 (2) | 2024.10.11 |
UML Sequence Diagram에서 Boundary, Control, Entity 클래스가 하는 역할은? (0) | 2024.05.09 |