의도
- 복잡한 객체를 생성하는 방법과 표현하는 방법을 정의하는 클래스를 별도로 분리하여, 서로 다른 표현이라도 이를 생성할 수 있는 동일한 절차를 제공할 수 있도록 함.
언제 쓰는가?
- 복합 객체의 생성 알고리즘이 이를 합성하는 요소 객체들이 무엇인지 이들의 조립 방법에 독립적일 때
- 합성할 객체들의 표현이 서로 다르더라도 생성 절차에서 이를 지원해야 할 때
구조
- Builder : Product 객체의 일부 요소들을 생성하기 위한 추상 인터페이스를 정의.
- ConctreteBuilder : Buider 클래스에 정의된 인터페이스를 구현하며, 제품의 부품들을 모아 빌더를 복합함. 생성한 요소의 표현을 정의하고 관리. 또한 제품을 검색하는 데 필요한 인터페이스 제공.
- Director : Builder 인터페이스를 사용하는 객체를 합성.
- Product : 생성할 복합 객체를 표현. ConcreteBuilder는 Product의 내부 표현을 구축하고 복합 객체가 어떻게 구성되는지에 관한 절차를 정의함.
Flow
- 사용자는 Director 객체를 생성, 이렇게 생성한 객체를 자신이 원하는 Builder 객체로 합성해 나감.
- 제품의 일부가 구축(built)될 때마다 Director는 Builder에 통보.
- Builder는 Director의 요청을 처리하여 제품에 부품을 추가.
- 사용자는 Builder에서 제품을 검색.
결과
- 제품에 대한 내부 표현을 다양하게 변화할 수 있음 : Builder 객체는 디렉터에게 제품을 복합하기 위해 필요한 추상 인터페이스를 제공함. 빌더를 사용함으로서 제품이 어떤 요소에서 복합되는지, 각 요소들의 표현 방법이 무엇인지 가릴 수 있음. 또한 새 제품에 대해선 Builder 클래스를 새로운 서브클래스로 정의하면 됨.
- 생성과 표현에 필요한 코드를 분리 : 복합 객체를 생성하고 복합 객체의 내부 표현 방법을 별도의 모듈로 정의할 수 있음. 사용자는 제품의 내부 구조는 전혀 몰라도 빌더와의 상호작용을 통해 복합 객체를 생성할 수 있음.
- 복합 객체를 생성하는 절차를 좀더 세밀하게 나눌 수 있음 : Builder는 Director의 통제 아래 하나씩 내부 구성요소들을 만들어 나감. 제품을 돌려받을 때 까지 이 과정이 반복됨. Builder 클래스의 인터페이스를 통해 제품을 생성하는 과정 자체가 반영되어 있음.
구현
- 조합과 구축에 필요한 인터페이스를 정의
- 제품에 대한 추상 클래스는 필요 없는가? : 제품마다 표현 방법이 다름. 너무 다른 객체에 대해 공통의 인터페이스를 가질 수는 없음
- Builder에 있는 메서드에 대해서는 구현을 제공하지 않는 게 일반적 : 서브 클래스에서 필요한 메서드만 재정의(순수 가상 함수로 정의 x)
예제 코드 ( 1 )
- 다양한 시계를 만들 수 있음을 예제로 구성하여 시계를 만드는 ClockBuilder(Builder)를 통해 새 시계를 만드는 과정을 세분화할 수 있었고, ClockMaker(Director)를 통해 특정 시계를 만들 수 있음을 나타냈음
- ClockMaker는 ClockBuilder가 제공하는 메서드 중 일부를 사용하지 않음으로서 다른 시계를 표현할 수 있었음
- 빌더와 추상 팩토리의 큰 차이는 Build 메서드가 내부에서 하는 일이 Director에게 감춰져 있음이라고 함
#include "Builder1.h"
int main()
{
ClockMaker clock_maker;
clock_maker.CreateStandardClock();
clock_maker.CreateNoHandClock();
clock_maker.ShowClocksInfo();
return 0;
}
- 일단 main의 관점에선 ClockMaker(Director)가 표준 시계와 시침 등이 없는 시계를 만들고 정보를 보여줌
- 실제 빌더 패턴의 적용은 CreateStandardClock과 CreateNoHandClock 메서드 내에서 ClockBuilder를 어떻게 활용하고 있는지를 봐야할 것
//Builder1.h
#pragma once
#include <vector>
#include "Builder1_Components.h"
class ClockBuilder
{
public:
virtual void BuildClock(float radius, std::string name) { }
virtual void BuildNumbers(int start, int end) { }
virtual void BuildHands() { }
};
class StandardClockBuilder : public ClockBuilder
{
public:
virtual void BuildClock(float radius, std::string name) override;
virtual void BuildNumbers(int start, int end) override;
virtual void BuildHands() override;
Clock* GetClock();
private:
Clock* clock = nullptr;
};
//목수
class ClockMaker
{
public:
Clock* CreateStandardClock();
Clock* CreateNoHandClock();
void ShowClocksInfo();
private:
std::vector<Clock*> clocks;
};
//Builder1.cpp
#include "Builder1.h"
void StandardClockBuilder::BuildClock(float radius, std::string name)
{
clock = new Clock;
clock->SetBodyRadius(radius);
clock->SetName(name);
}
void StandardClockBuilder::BuildNumbers(int start, int end)
{
if (clock)
{
clock->AddNumbers(start, end);
}
}
void StandardClockBuilder::BuildHands()
{
if (clock)
{
ClockHand* hour_hand = new ClockHand;
hour_hand->handtype = HandType::hour;
clock->SetHourHand(hour_hand);
ClockHand* minute_hand = new ClockHand;
minute_hand->handtype = HandType::minute;
clock->SetMinuteHand(minute_hand);
ClockHand* second_hand = new ClockHand;
second_hand->handtype = HandType::second;
clock->SetSecondHand(second_hand);
}
}
Clock* StandardClockBuilder::GetClock()
{
return clock;
}
Clock* ClockMaker::CreateStandardClock()
{
StandardClockBuilder clock_builder;
clock_builder.BuildClock(10.f, "standard_clock");
clock_builder.BuildNumbers(1, 12);
clock_builder.BuildHands();
clocks.push_back(clock_builder.GetClock());
return clocks.back();
}
Clock* ClockMaker::CreateNoHandClock()
{
StandardClockBuilder clock_builder;
clock_builder.BuildClock(20.f, "no_hand_clock");
clock_builder.BuildNumbers(1, 4);
clocks.push_back(clock_builder.GetClock());
return clocks.back();
}
void ClockMaker::ShowClocksInfo()
{
for (auto& clock : clocks)
{
clock->ShowClockInfo();
}
}
- StandardClockBuilder는 반지름과 시계의 이름을 정하며 Clock을 만들어내고, 이후 추가적인 Build 메서드들을 통해 Clock에 부가 정보를 새겨넣을 수 있음
- 이 때 ClockMaker의 두 Create 메서드를 보게 되면 Clock을 만들어주는 인자가 다르고 호출하는 메서드의 개수도 다름
- 최종적으로 다 만들어진 Clock에 대해 GetClock 메서드를 통해 얻어오는 순간이 ClockBuilder를 이용하여 새로운 Clock을 만들어 낸 것이라 할 수 있음
- 예제의 길이를 위해 메서드를 더 부르거나 덜 부르는 식으로 다른 시계를 만들어 냄을 나타냈으나 실제 GoF의 디자인 패턴에서 설명하길 다른 시계를 만들기 위해선 Builder의 서브 클래스를 새로 만드는 것이 더 나음. (StandardClockBuilder 외에 새로운 ClockBuilder를 설계하라는 뜻)
//Builder1_Components.h
#pragma once
#include <vector>
#include <iostream>
enum class HandType
{
second,
minute,
hour
};
class ClockHand
{
public:
HandType handtype;
};
class Clock
{
public:
void ShowClockInfo()
{
using namespace std;
cout << "clock_name : " << name << endl;
cout << "body_radius : " << body_radius << endl;
cout << "numbers : ";
for (int num : numbers)
{
cout << num << " ";
}
cout << "\n";
if (hour_hand)
{
cout << "hour_hand : " << (int)hour_hand->handtype << endl;
}
if (minute_hand)
{
cout << "minute_hand : " << (int)minute_hand->handtype << endl;
}
if (second_hand)
{
cout << "second_hand : " << (int)second_hand->handtype << endl;
}
cout << endl;
}
public:
void SetName(std::string name)
{
this->name = name;
}
void AddNumbers(int start, int end)
{
for (int i = start; i <= end; i++)
{
numbers.push_back(i);
}
}
void SetHourHand(ClockHand* hour_hand)
{
this->hour_hand = hour_hand;
}
void SetMinuteHand(ClockHand* minute_hand)
{
this->minute_hand = minute_hand;
}
void SetSecondHand(ClockHand* second_hand)
{
this->second_hand = second_hand;
}
void SetBodyRadius(float body_radius)
{
this->body_radius = body_radius;
}
private:
std::string name;
std::vector<int> numbers;
ClockHand* hour_hand;
ClockHand* minute_hand;
ClockHand* second_hand;
float body_radius;
};
- 패턴과는 관련 없는 이해를 돕기 위한 부가적인 코드.
- 시계의 숫자를 이루는 numbers, 시침, 분침, 초침에 해당하는 hand들, 시계의 반지름에 해당하는 body_radius 정도로 이루어져 있음
예제 코드 ( 2 )
- 위와 완벽히 같은 결과를 보이나 Builder를 사용함에 있어 더 편리하게 사용할 수 있는 방법으로 재구성
- Builder는 this를 반환함으로서 연쇄적으로 build 가능
#include "Builder2.h"
int main()
{
ClockMaker_V2 clock_maker_v2;
clock_maker_v2.CreateStandardClock();
clock_maker_v2.CreateNoHandClock();
clock_maker_v2.ShowClocksInfo();
return 0;
}
//Builder2.h
#pragma once
#include <vector>
#include "Builder2_Components.h"
class ClockBuilder_V2
{
public:
virtual ClockBuilder_V2& BuildClock(float radius, std::string name) { return *this; }
virtual ClockBuilder_V2& BuildNumbers(int start, int end) { return *this; }
virtual ClockBuilder_V2& BuildHands() { return *this; }
};
class StandardClockBuilder_V2 : public ClockBuilder_V2
{
public:
virtual ClockBuilder_V2& BuildClock(float radius, std::string name) override;
virtual ClockBuilder_V2& BuildNumbers(int start, int end) override;
virtual ClockBuilder_V2& BuildHands() override;
Clock_V2* MakeNewClock();
private:
float radius = 10.f;
std::string name = "None";
int start = 1, end = 12;
ClockHand_V2* hour_hand = nullptr;
ClockHand_V2* minute_hand = nullptr;
ClockHand_V2* second_hand = nullptr;
};
//목수
class ClockMaker_V2
{
public:
Clock_V2* CreateStandardClock();
Clock_V2* CreateNoHandClock();
void ShowClocksInfo();
private:
std::vector<Clock_V2*> clocks;
};
//Builder2.cpp
#include "Builder2.h"
ClockBuilder_V2& StandardClockBuilder_V2::BuildClock(float radius, std::string name)
{
this->radius = radius;
this->name = name;
return *this;
}
ClockBuilder_V2& StandardClockBuilder_V2::BuildNumbers(int start, int end)
{
this->start = start;
this->end = end;
return *this;
}
ClockBuilder_V2& StandardClockBuilder_V2::BuildHands()
{
ClockHand_V2* hour_hand = new ClockHand_V2;
hour_hand->handtype = HandType::hour;
this->hour_hand = hour_hand;
ClockHand_V2* minute_hand = new ClockHand_V2;
minute_hand->handtype = HandType::minute;
this->minute_hand = minute_hand;
ClockHand_V2* second_hand = new ClockHand_V2;
second_hand->handtype = HandType::second;
this->second_hand = second_hand;
return *this;
}
Clock_V2* StandardClockBuilder_V2::MakeNewClock()
{
Clock_V2* clock = new Clock_V2;
clock->SetName(name);
clock->SetBodyRadius(radius);
clock->AddNumbers(start, end);
clock->SetHourHand(hour_hand);
clock->SetMinuteHand(minute_hand);
clock->SetSecondHand(second_hand);
return clock;
}
Clock_V2* ClockMaker_V2::CreateStandardClock()
{
StandardClockBuilder_V2 clock_builder;
clock_builder
.BuildClock(10.f, "standard_clock")
.BuildNumbers(1, 12)
.BuildHands();
clocks.push_back(clock_builder.MakeNewClock());
return clocks.back();
}
Clock_V2* ClockMaker_V2::CreateNoHandClock()
{
StandardClockBuilder_V2 clock_builder;
clock_builder
.BuildClock(20.f, "no_hand_clock")
.BuildNumbers(1, 4);
clocks.push_back(clock_builder.MakeNewClock());
return clocks.back();
}
void ClockMaker_V2::ShowClocksInfo()
{
for (auto& clock : clocks)
{
clock->ShowClockInfo();
}
}
- this를 반환함으로서 builder는 연쇄적으로 build 메서드들을 호출할 수 있음
- 이러한 구성으로 바꾸기 위해 Builder 클래스는 세팅하기 위한 값들을 캐싱해두었다가 실제 Product가 만들어지는 순간에 캐싱해두었던 값들을 세팅해줌
'디자인 패턴 > 생성' 카테고리의 다른 글
단일체(Singleton) (0) | 2023.12.19 |
---|---|
원형(Prototype) (0) | 2023.12.19 |
팩토리 메서드(Factory Method) (2) | 2023.12.18 |
추상 팩토리(Abstract Factory) (0) | 2023.12.15 |
생성 패턴 (0) | 2023.12.15 |