의도

  • 복잡한 객체를 생성하는 방법과 표현하는 방법을 정의하는 클래스를 별도로 분리하여, 서로 다른 표현이라도 이를 생성할 수 있는 동일한 절차를 제공할 수 있도록 함.

 

언제 쓰는가?

  • 복합 객체의 생성 알고리즘이 이를 합성하는 요소 객체들이 무엇인지 이들의 조립 방법에 독립적일 때
  • 합성할 객체들의 표현이 서로 다르더라도 생성 절차에서 이를 지원해야 할 때

 

구조

GoF의 디자인 패턴 중 P.146

  • Builder : Product 객체의 일부 요소들을 생성하기 위한 추상 인터페이스를 정의.
  • ConctreteBuilder : Buider 클래스에 정의된 인터페이스를 구현하며, 제품의 부품들을 모아 빌더를 복합함. 생성한 요소의 표현을 정의하고 관리. 또한 제품을 검색하는 데 필요한 인터페이스 제공.
  • Director : Builder 인터페이스를 사용하는 객체를 합성.
  • Product : 생성할 복합 객체를 표현. ConcreteBuilder는 Product의 내부 표현을 구축하고 복합 객체가 어떻게 구성되는지에 관한 절차를 정의함.

 

Flow

GoF의 디자인 패턴 중 P.147

  • 사용자는 Director 객체를 생성, 이렇게 생성한 객체를 자신이 원하는 Builder 객체로 합성해 나감.
  • 제품의 일부가 구축(built)될 때마다 Director는 Builder에 통보.
  • Builder는 Director의 요청을 처리하여 제품에 부품을 추가.
  • 사용자는 Builder에서 제품을 검색.

 

결과

  1. 제품에 대한 내부 표현을 다양하게 변화할 수 있음 : Builder 객체는 디렉터에게 제품을 복합하기 위해 필요한 추상 인터페이스를 제공함. 빌더를 사용함으로서 제품이 어떤 요소에서 복합되는지, 각 요소들의 표현 방법이 무엇인지 가릴 수 있음. 또한 새 제품에 대해선 Builder 클래스를 새로운 서브클래스로 정의하면 됨.
  2. 생성과 표현에 필요한 코드를 분리 : 복합 객체를 생성하고 복합 객체의 내부 표현 방법을 별도의 모듈로 정의할 수 있음. 사용자는 제품의 내부 구조는 전혀 몰라도 빌더와의 상호작용을 통해 복합 객체를 생성할 수 있음. 
  3. 복합 객체를 생성하는 절차를 좀더 세밀하게 나눌 수 있음 : Builder는 Director의 통제 아래 하나씩 내부 구성요소들을 만들어 나감. 제품을 돌려받을 때 까지 이 과정이 반복됨. Builder 클래스의 인터페이스를 통해 제품을 생성하는 과정 자체가 반영되어 있음. 

 

구현

  1. 조합과 구축에 필요한 인터페이스를 정의
  2. 제품에 대한 추상 클래스는 필요 없는가? : 제품마다 표현 방법이 다름. 너무 다른 객체에 대해 공통의 인터페이스를 가질 수는 없음
  3. 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;
}

ShowClocksInfo 메서드의 결과

  • 일단 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

+ Recent posts