1. 언제 쓰는 지 비유하자면?
우리가 옷을 산다고 생각해보자.
옷은 크기별로 다르다. S ,M, L 등이 있고, 색도 각기 다르며, 소재도 다를 수 있다. 사려고 하는 옷의 분류가 크기, 색, 소재로 분류된다고 생각하고, 각각의 옵션이 3개씩 있다고 생각하면, 총 27가지의 경우의 수가 존재한다.
그렇다면 그런 옷에 맞는 각각의 class들을 모두 새롭게 정의해야할까? 이는 매우 번거롭다.
그래서 우리는 Builder 패턴을 만드는 것이다.
하나의 예시를 만들어보겠다.
※뭔가 빈약한데 나중에 더 공부해서 수정하겠다...
class Builder{
virtual ~Builder(){}
virtual void take_option_c() const = 0;
virtual void take_option_s() const = 0;
virtual void take_option_f() const = 0;
}
class customize_Color : public Builder{
public:
string color, size, fabric;
void take_option_c() const{
std::cin >> this -> color;
}
void take_option_s() const{
this -> size = "L";
}
void take_option_f() const{
this -> fabric = "cotton";
}
}
class customize_Size : public Builder{
public:
string color, size, fabric;
void take_option_c() const{
this -> color = "red";
}
void take_option_s() const{
std::cin >> this -> size;
}
void take_option_f() const{
this -> fabric = "cotton";
}
}
class customize_Fabric : public Builder{
public:
string color, size, fabric;
void take_option_c() const{
this -> color = "red";
}
void take_option_s() const{
this -> size = "L";
}
void take_option_f() const{
std::cin >> this -> fabric;
}
}
class Director(){
private:
Builder* builder;
public:
void setBuilder(Builder* builder){
this->builder = builder;
}
void makecloth(){
this->builder->take_option_c();
this->builder->take_option_s();
this->builder->take_option_f();
}
}
위 같은 맥락으로 Builder pattern은 구성된다.
customize가 붙은 함수는 하나의 옵션만 변경가능하게 구성하고, 나머지는 임의로 값들을 지정해준다. 이후에 옷을 고를 때, 우리는 setBuilder()로 정해준 빌더와 함께 옷을 고를 수 있다.
장점은 추가적인 경우 발생 시, 대처에 대해 유능하다. 만약에 색과 크기를 custom하고 싶은 경우라면, 다음과 같은 class를 추가해주기만 하면 된다.
class customize_Color_and_Size : public Builder{
public:
string color, size, fabric;
void take_option_c() const{
std::cin >> this -> color;
}
void take_option_s() const{
std::cin >> this -> size;
}
void take_option_f() const{
this -> fabric = "cotton";
}
}
2. 정리
생성자를 일일이 생성하기 힘들 때, 하나의 완전히 추상적인 class(이는 Builder에 해당한다.)를 생성하고, 이를 상속받는 class를 생성하여, 각각이 함수를 override하는 구성을 띈다.
다른 class(Director에 해당한다.)는 Builder의 포인터 객체를 멤버로 가지고 있어, 동적으로 할당 가능하고, 함수의 override 덕분에, 또 추가가 편리하다는 장점 덕분에 더욱 간편한 구성을 가진다.
Abstract 클래스를 사용하여 아래 그림과 같이 모든 패키지의 베이스가되는 하나의 abstract 클래스 Vaction을 만들고, 이 클래스를 기반으로 Package1, Packge2, Packge3을 나타내는 3개의 클래스를 만들어 본다.
아래 주어진 main.cpp, director.h, director.cpp파일을 사용하고, 우리는 builder.cpp파일을 구현해내야한다.
//main 함수//
#include "director.h"
int main()
{
// 여행 유형 선택
int type = 1;
while (type >= 1 && type <= 3) {
std::cout << "Enter the type of vacation (1, 2, 3): ";
std::cin >> type;
// 여행 계획 확인
Director::book(type);
}
return 0;
}
//director.hpp 파일 내용//
#ifndef DIRECTOR_H
#define DIRECTOR_H
#include "builder.h"
// 정적 멤버를 갖는 Director 클래스
class Director
{
public:
static Vacation* vacation;
static void book(int type); // 클라이언트는 이 함수만 호출 가능
};
#endif
//director.cpp 파일 내용//
#include "director.h"
// 정적 데이터 멤버 정의
Vacation * Director::vacation = 0;
// 클라이언트가 호출할 정적 멤버 함수 정의
void Director::book(int packageType)
{
if (packageType == 1)
{
vacation = new Package1();
}
else if (packageType == 2)
{
vacation = new Package2();
}
else if (packageType == 3)
{
vacation = new Package3();
}
std::cout << "This is information about your vacation: " << std::endl;
vacation->bookHotels();
vacation->bookFlights();
vacation->bookTours();
std::cout << std::endl;
}
그림에서도 볼 수 있듯이, Vacation class를 상속받는 3개의 package 클래스를 생성해야한다. Vacation 클래스가 abstract하기 때문에(Builder에 해당하기 때문에!), Package1, 2, 3(위에서 공부했을 때, 코드 상 customize_Color, 사진상 A에 해당하는 것!)에서 bookHotels(), bookFlights(), bookTours()(위에서 take_option_s, take_option_c 등에 해당하는 것!)를 override하는 코드를 작성해보자.
+ hpp파일을 생성할 때 주의 사항!
#ifndef hppfilename_H
#define hppfilename_H
/*
내용 작성
*/
#endif
항상 이 형식을 유지해주어야함. #ifndef, #define 뒤에 나오는 이름의 경우, 파일명과 꼭 일치할 필요는 없지만, 관례상, 파일이름_H 형식을 유지하는 것이 좋음!
이렇게 생성한 hpp파일을 include할 시에는 다음을 참고한다.
#include "hppfilename" (O)
=>사용자 커스텀 파일이기에, ""안에 넣어주어야함!
#include <hppfilename> (X)
=>STL(Standard Template Library)이기에, <>안에 넣어주어야함!
#ifndef builder_H
#define builder_H
#include <iostream>
#include <string>
#include <cassert>
class Vacation {
protected:
std::string hotel = "Hotels are booked.";
std::string flight = "Flights are booked.";
std::string tour = "Tours are booked.";
public:
Vacation(){}
virtual void bookHotels() const =0;
virtual void bookFlights() const =0;
virtual void bookTours() const =0;
};
class Package1 : public Vacation {
public:
Package1(){}
void bookHotels() const override {
std::cout << hotel << std::endl;
};
void bookFlights() const override {
std::cout << flight << std::endl;
};
void bookTours() const override {
std::cout << "Run:";
};
};
class Package2 : public Vacation {
public:
Package2(){}
void bookHotels() const override {
std::cout << hotel << std::endl;
};
void bookFlights() const override {};
void bookTours() const override {
std::cout << tour;
};
};
class Package3 : public Vacation {
public:
Package3(){}
void bookHotels() const override {
std::cout << hotel << std::endl;
};
void bookFlights() const override {
std::cout << flight << std::endl;
};
void bookTours() const override {
std::cout << tour;
};
};
#endif