Builder
Sample Code
We'll define a variant of the
CreateMaze
member function
that takes a builder of class MazeBuilder as an argument.
The MazeBuilder class defines the following interface for
building mazes:
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();
};
This interface can create three things: (1) the maze, (2) rooms with a
particular room number, and (3) doors between numbered rooms. The
GetMaze operation returns the maze to the client.
Subclasses of MazeBuilder will override this operation to
return the maze that they build.
All the maze-building operations of MazeBuilder do nothing
by default. They're not declared pure virtual to let derived classes
override only those methods in which they're interested.
Given the MazeBuilder interface, we can change the
CreateMaze member function to take this builder as a
parameter.
Maze* MazeGame::CreateMaze (MazeBuilder& builder) {
builder.BuildMaze();
builder.BuildRoom(1);
builder.BuildRoom(2);
builder.BuildDoor(1, 2);
return builder.GetMaze();
}
Compare this version of CreateMaze with the original.
Notice how the builder hides the internal representation of the
Maze---that is, the classes that define rooms, doors, and walls---and
how these parts are assembled to complete the final maze. Someone
might guess that there are classes for representing rooms and doors,
but there is no hint of one for walls. This makes it easier to change
the way a maze is represented, since none of the clients of
MazeBuilder has to be changed.
Like the other creational patterns, the Builder pattern encapsulates
how objects get created, in this case through the interface defined by
MazeBuilder. That means we can reuse MazeBuilder
to build different kinds of mazes. The CreateComplexMaze
operation gives an example:
Maze* MazeGame::CreateComplexMaze (MazeBuilder& builder) {
builder.BuildRoom(1);
// ...
builder.BuildRoom(1001);
return builder.GetMaze();
}
Note that MazeBuilder does not create mazes itself; its
main purpose is just to define an interface for creating mazes. It
defines empty implementations primarily for convenience. Subclasses of
MazeBuilder do the actual work.
The subclass StandardMazeBuilder is an implementation that
builds simple mazes. It keeps track of the maze it's building in the
variable _currentMaze.
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;
};
CommonWall is a utility operation that determines
the direction of the common wall between two rooms.
The StandardMazeBuilder constructor simply initializes
_currentMaze.
StandardMazeBuilder::StandardMazeBuilder () {
_currentMaze = 0;
}
BuildMaze instantiates a Maze that
other operations will assemble and eventually return to the client
(with GetMaze).
void StandardMazeBuilder::BuildMaze () {
_currentMaze = new Maze;
}
Maze *StandardMazeBuilder::GetMaze () {
Maze* maze = _currentMaze;
return maze;
}
The BuildRoom operation creates a room and builds the
walls around it:
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);
}
}
To build a door between two rooms, StandardMazeBuilder looks
up both rooms in the maze and finds their adjoining 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);
}
Clients can now use CreateMaze in conjunction with
StandardMazeBuilder to create a maze:
Maze* maze;
MazeGame game;
StandardMazeBuilder builder;
game.CreateMaze(builder);
maze = builder.GetMaze();
We could have put all the StandardMazeBuilder operations in
Maze and let each Maze build itself. But making
Maze smaller makes it easier to understand and modify, and
StandardMazeBuilder is easy to separate from Maze.
Most importantly, separating the two lets you have a variety of
MazeBuilders, each using different classes for rooms, walls,
and doors.
A more exotic MazeBuilder is
CountingMazeBuilder. This builder doesn't create a
maze at all; it just counts the different kinds of components that
would have been created.
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;
};
The constructor initializes the counters, and the overridden
MazeBuilder operations increment them accordingly.
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;
}
Here's how a client might use a CountingMazeBuilder:
int rooms, doors;
MazeGame game;
CountingMazeBuilder builder;
game.CreateMaze(builder);
builder.GetCounts(rooms, doors);
cout << "The maze has "
<< rooms << " rooms and "
<< doors << " doors" << endl;