끝나지 않는 프로그래밍 일기

Builder pattern을 사용하면 제품을 여러 단계로 나눠서 만들 수 있도록 제품 생산 단계들을 캡슐화 할 수 있다.


구조

UML 클래스 다이어그램


  • Builder: client에서는 추상 인터페이스를 통해서 Product 객체의 일부 요소들을 생성한다.

  • ConcreteBuilder: Builder 클래스에 정의된 인터페이스를 구현하며, 제품의 부품들을 모아 빌더를 복합한다. 특정 종류의 제품을 생성하고 조립하는 데 필요한 모든 코드가 들어간다.

  • Director: Builder 인터페이스를 사용하는 객체를 합성한다.

  • Product: 생성할 복합 객체를 표현한다.


장단점

  1. 제품에 대한 내부 표현을 다양하게 변화시킬 수 있다. 
  2. 생성과 표현에 필요한 코드를 분리한다. Builder pattern을 사용하면, 복합 객체를 생성하고 복합 객체의 내부 표현 방법을 별도의 모듈로 정의할 수 있다. 따라서, 제품의 내부 구조를 client로부터 보호할 수 있다.
  3. Factory pattern에서는 한 단계에서 모든 걸 처리해야만 했던 것이, Builder pattern에서는 여러 단계와 다양한 절차를 통해서 객체를 만들 수 있다. Builder pattern은 복잡한 객체의 단계별 생성에 중점을 둔 반면에, Factory pattern은 제품의 유사군들이 존재할 때 유연한 설계에 중점을 둔다. Builder pattern은 생성의 마지막 단계에서 생성한 제품을 반환하는 반면, Factory pattern에서는 만드는 즉시 제품을 반환한다. Factory pattern에서 만드는 제품은 꼭 모여야만 의미 있는 것이 아니라 하나만으로 의미가 있기 때문이다.
  4. Factory pattern을 사용하는 경우에 비해 객체를 만들기 위해서 client에 대해 더 많이 알아야 한다.


예시

아래에서 MazeBuilder 클래스에 있는 메서드에 대해서는 구현을 제공하지 않는다. 또한, pure virtual function으로 정의하지도 않는다. 이는 Builder의 자식 클래스에서 관심이 있을 때만 이들 연산을 재정의할 수 있도록 하기 위함이다. 아래에 정의된 메서드들을 보면 어떤 종류의 미로인지, 어떤 방법으로 만들어지는지 전혀 알 수 없다. 메서드의 이름을 통해서 어떤 종류의 요소들을 생성할 수 있는지를 표현할 뿐이다.

class MazeBuilder {
public:
	virtual void BuildMaze() { }
	virtual void BuildRoom(int room) { }
	virtual void BuildDoor(int roomFrom, int roomTo) { }

	virtual Maze* GetMaze() { return 0; }
};

아래의 예시와 abstract factory pattern의 예시를 서로 비교해보면 Builder 객체가 미로의 내부 표현을 어떻게 은닉하는지 알 수 있다. 

Maze* MazeGame::CreateMaze(MazeBuilder& builder) {
	builder.BuildMaze();

	builder.BuildRoom(1);
	builder.BuildRoom(2);
	builder.BuildDoor(1, 2);

	return builder.GetMaze();
}

간단한 미로를 구축하기 위해 MazeBuilder 클래스를 상속받는 StandardMazeBuilder 자식 클래스를 살펴보자.

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;
};

각각의 메서드를 구현해보면 아래와 같다.

StandardMazeBuilder::StandardMazeBuilder() {
	_currentMaze = 0;
}

void StandardMazeBuilder::BuildMaze() {
	_currentMaze = new Maze;
}

Maze* StandardMazeBuilder::GetMaze() {
	return _currentMaze;
}

void StandardMazeBuilder::BuildRoom(int 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);
}

이번에는 미로를 생성하지 않고 단지 생성된 미로의 서로 다른 종류의 구성요소의 수를 알아내기만 하는 CountingMazeBuilder 클래스다.

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;
};

바로 구현부를 살펴보도록 하자.

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;
}

BuildRoom 메서드가 호출될 때마다 방의 개수가 하나씩 증가하고, BuildDoor 메서드가 호출될 때마다 문의 개수가 하나씩 증가한다. 아래와 같이 CountingMazeBuilder를 통해 CreateMaze 메서드에서 정의했던 미로의 구조에 방과 문이 각각 몇개씩 들어가 있는지 확인할 수 있다.

MazeGame game;
CountingMazeBuilder builder;

game.CreateMaze(builder);
builder.GetCounts(rooms, doors);

cout << "미로는 " << rooms << "개의 방과 " << doors << "개의 문을 가지고 있습니다." << endl;


'메모 > Design pattern' 카테고리의 다른 글

메모. Proxy pattern  (0) 2019.11.18
메모. Facade pattern  (0) 2019.11.17
메모. Factory method pattern  (0) 2019.05.19
메모. Builder pattern  (0) 2019.05.18
메모. Concrete class  (1) 2019.05.13
메모. Abstract factory pattern  (0) 2019.05.13