빌더 (Builder)
1. 의도
- 비슷한 생산 절차를 가지는 객체들을 묶어 Builder를 통해 추상화하고 동일한 절차를 제공할 수있도록 한다.
2. 활용
- 생산 절차는 비슷하지만 생성된 객체들은 서로 독립적인 경우 (공통으로 상속하는 추상 클래스가 없어도 된다.)
- cf) 추상 팩토리 - 한 팩토리릍 통해 생산된 객체는 다른 팩토리에서 생산된 객체와 공통으로 상속하는 추상 클래스가 존재한다.
- 따라서 표현이 다르지만 생산 절차가 비슷하기 때문에 생산 절차에 대한 인터페이스를 제공한다.
ex) RTFReader 문서 변환기
- RTFReader는 문서를 읽어 토큰의 타입에 따라 자신이 가지고 있는 빌더를 통해 원하는 변환을 수행할 수 있다.
- RTFReader는 디렉터로서 빌더를 합성하고(has-a 관계, 상속 x) 변환 알고리즘을 TextConverter라는 빌더를 통해 실행한다.
- 변환기는 ASCIIText를 생산하는 ASCIIConverter, TeXText를 생산하는 TeXConverter등으로 구체화될 수 있고 동일한 생산 절차를 가진다.
- 빌더를 통해 생산되는 객체는 서로 독립적이다.
3. 참여자
- Builder: Product 객체의 일부 요소를 생성하기 위한 인터페이스 정의
- ConcreteBuilder: 빌더에 정의된 인터페이스를 구현하고 Product의 생산 절차를 정의
- Director: 빌더 인터페이스를 사용하는 객체를 합성
- Product: 생성할 복합 객체
4. 결과
- 제품에 대한 표현을 다양하게 할 수 있음: 제품에 대한 표현과 생산 절차가 달라질 때마다 새로운 서브클래스를 정의하면 된다.
- 생성과 표현에 필요한 코드를 분리: 사용자는 빌더가 복합 객체를 생성하기 때문에 제품 내부 구조를 알 필요가 없다.
- 복합 객체를 생성하는 절차를 좀 더 세밀하게 나눌 수 있음: 빌더가 정의한 생산 절차에 따라 객체를 생성한다.
5. 구현
1) 조합과 구축에 필요한 인터페이스를 정의한다.
단계별로 제품을 생산하기 위해 모든 종류의 제품을 생성 과정에 필요한 일반화된 연산을 정의함.
2) 제품에 대한 추상 클래스는 필요없다.
제품은 서로 독립적이므로 제품에 대한 추상화는 관련이 없음. 빌더는 생산 절차에 대한 추상화 과정임.
3) 빌더에 있는 메서드는 구현을 제공하지 않는다.
서브 클래스에서 필요한 메서드만 구현할 수 있도록 한다. -> 순수 가상 함수로 정의하지 않는다.
예시코드
추상 팩토리 편의 CreateMaze 함수를 고쳐서 MazeBuilder 클래스를 이용하여 미로를 생성해보자.
Builder
1
2
3
4
5
6
7
8
9
10
|
class MazeBuilder
{
public:
virtual void BuildMaze() {}
virtual void BuildRoom(int room) {}
virtual void BuildDoor(int roomFrom, int roomTo) {}
virtual Maze* GetMaze() { return 0; }
protected:
MazeBuilder();
};
|
cs |
생산 절차
미로 -> 방 -> 방을 잊는 문을 만드는 절차를 가지고 만들어진 Maze 객체를 반환하는 GetMaze() 연산이 있다.
메서드를 구현하지 않고 순수 가상 함수로 정의하지 않는 것에 주의
MazeGame의 CreateMaze()
1
2
3
4
5
6
7
8
9
|
Maze* MazeGame::CreateMaze(MazeBuilder& builder)
{
builder.BuildMaze();
builder.BuildRoom(1);
builder.BuildRoom(2);
builder.BuildDoor(1, 2);
return builder.GetMaze();
}
|
cs |
- 기존 추상 팩토리 편에서 MazeFactory 객체를 받아 방, 문, 벽등의 객체를 생산하였는데
- 이 과정을 모두 은닉화하여 builder 패턴에서는 생산 절차만 남게 된다.
- 따라서 구체적으로 문 객체를 생성해서 어떤 두 방을 연결하는 지 등의 구체적인 메서드가 나타나지 않는다.
- 또한 빌더의 생산 절차를 상속하여 구체적으로 구현하는 서브 클래스를 정의해야한다.
1. StandardMazeBuilder
MazeBuilder를 상속하여 간단한 미로를 구축하는 서브 클래스를 정의해보자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
#include "MazeBuilder.h"
class StandardMazeBuilder : public MazeBuilder
{
public:
StandardMazeBuilder();
virtual void BuildMaze();
virtual void BuildRoom(int);
virtual void BuildDoor(int, int);
virtual Maze* GetMaze();
private:
// 두 방 사이의 공통 벽 방향을 반환
Direction CommonWall(Room*, Room*);
Maze* _currentMaze;
};
|
cs |
StandardMazeBuilder는 빌더의 인터페이스를 모두 구현하고 기본적인 미로를 생성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
#include "StandardMazeBuilder.h"
StandardMazeBuilder::StandardMazeBuilder()
{
_currentMaze = 0;
}
Maze* StandardMazeBuilder::GetMaze()
{
return _currentMaze;
}
void StandardMazeBuilder::BuildRoom(int n)
{
// n번 방번호가 없다면 방을 생성한다.
if (!_currentMaze->RoomNo(n))
{
Room* room = new Room(n);
_currentMaze->AddRoom(room);
room->SetSide(North, new Wall);
room->SetSide(South, new Wall);
room->SetSide(East, new Wall);
room->SetSide(West, new Wall);
}
}
void StandardMazeBuilder::BuildDoor(int n1, int n2)
{
Room* r1 = _currentMaze->RoomNo(n1);
Room* r2 = _currentMaze->RoomNo(n2);
Door* d = new Door(r1, r2);
r1->SetSide(CommonWall(r1, r2), d);
r2->SetSide(CommonWall(r2, r1), d);
}
|
cs |
BuildRoom과 BuildDoor 메서드를 통해 구체적으로 생성하는 과정을 구현하고 있다.
2. CountingMazeBuilder
또 다른 예시로, 미로를 생성하지 않고 생성 과정에서 미로를 구성하는 요소 갯수를 파악하는 특수한 빌더이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
#include "MazeBuilder.h"
class CountingMazeBuilder : public MazeBuilder
{
public:
CountingMazeBuilder();
virtual void BuildMaze();
virtual void BuildRoom(int);
virtual void BuildDoor(int, int);
virtual void AddWall(int, Direction);
void GetCounts(int&, int&) const;
private:
int _doors;
int _rooms;
};
|
cs |
구현 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#include "CountingMazeBuilder.h"
CountingMazeBuilder::CountingMazeBuilder()
{
_rooms = _doors = 0;
}
void CountingMazeBuilder::BuildRoom(int)
{
_rooms++;
}
void CountingMazeBuilder::BuildDoor(int, int)
{
_doors++;
}
void CountingMazeBuilder::GetCounts(int& rooms, int& doors) const
{
rooms = _rooms;
doors = _doors;
}
|
cs |
방과 문 갯수를 파악하고 있다.
StandardMazeBuilder를 이용한 미로 생성 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
int main()
{
Maze* myMaze;
MazeGame game;
StandardMazeBuilder builder;
// concrete factory to produce concrete products
//BombedMazeFactory factory;
//Maze* myMaze = game.CreateMaze(factory);
game.CreateMaze(builder);
myMaze = builder.GetMaze();
return 0;
}
|
cs |
- 추상 팩토리와 비슷한 방식으로 생산과 관련된 내용이 사용자 코드에서 드러나지 않는다.
- game객체의 CreateMaeze()에 매개변수로 전달하여 원하는 미로를 생산할 수 있다.
빌더 패턴 vs 추상 팩토리 패턴
빌더 패턴은 추상 팩토리 패턴과 비슷한 모습을 보이고 있는데,
- 빌더 패턴은 생산 절차에 좀 더 중점을 두고 추상 팩토리는 유사 제품을 하나로 묶는데 중점을 둔다.
- 또 다른 차이로 빌더 패턴에서는 생성한 객체를 바로 반환하지 않지만 추상 팩토리에서는 생성한 객체를 return을 통해 바로 반환한다.
- 사용자가 빌더를 통해 객체를 접근하는 방식으로 쓴다면 빌더 패턴을
- 공장처럼 제품을 생산하여 하나의 객체로써 의미가 크다면 추상 팩토리 패턴 쪽이 가깝다고 생각할 수 있다.
참고 자료 : GoF 디자인 패턴
'디자인 패턴 > GoF' 카테고리의 다른 글
[디자인 패턴] 구조 패턴 (1) 적응자 Adapter (0) | 2021.01.26 |
---|---|
[디자인 패턴] 생성 패턴 (5) 싱글톤 Singleton (2021.02.19 수정) (0) | 2021.01.25 |
[디자인 패턴] 생성 패턴 (4) 원형 Prototype (0) | 2021.01.25 |
[디자인 패턴] 생성 패턴 (3) 팩토리 메서드 Factory Method (0) | 2021.01.24 |
[디자인 패턴] 생성 패턴 (1) 추상 팩토리 Abstract Factory (0) | 2021.01.24 |