Separate the construction of a complex object from its representation so that the same construction process can create different representations.
A reader for the RTF (Rich Text Format) document exchange format should be able to convert RTF to many text formats. The reader might convert RTF documents into plain ASCII text or into a text widget that can be edited interactively. The problem, however, is that the number of possible conversions is open-ended. So it should be easy to add a new conversion without modifying the reader.
A solution is to configure the RTFReader class with a TextConverter object that converts RTF to another textual representation. As the RTFReader parses the RTF document, it uses the TextConverter to perform the conversion. Whenever the RTFReader recognizes an RTF token (either plain text or an RTF control word), it issues a request to the TextConverter to convert the token. TextConverter objects are responsible both for performing the data conversion and for representing the token in a particular format.
Subclasses of TextConverter specialize in different conversions and formats. For example, an ASCIIConverter ignores requests to convert anything except plain text. A TeXConverter, on the other hand, will implement operations for all requests in order to produce a TeX representation that captures all the stylistic information in the text. A TextWidgetConverter will produce a complex user interface object that lets the user see and edit the text.
Each kind of converter class takes the mechanism for creating and assembling a complex object and puts it behind an abstract interface. The converter is separate from the reader, which is responsible for parsing an RTF document.
The Builder pattern captures all these relationships. Each converter class is called a builder in the pattern, and the reader is called the director. Applied to this example, the Builder pattern separates the algorithm for interpreting a textual format (that is, the parser for RTF documents) from how a converted format gets created and represented. This lets us reuse the RTFReader's parsing algorithm to create different text representations from RTF documentsjust configure the RTFReader with different subclasses of TextConverter.
Use the Builder pattern when
The following interaction diagram illustrates how Builder and Director cooperate with a client.
Here are key consequences of the Builder pattern:
Each ConcreteBuilder contains all the code to create and assemble a particular kind of product. The code is written once; then different Directors can reuse it to build Product variants from the same set of parts. In the earlier RTF example, we could define a reader for a format other than RTF, say, an SGMLReader, and use the same TextConverters to generate ASCIIText, TeXText, and TextWidget renditions of SGML documents.
Typically there's an abstract Builder class that defines an operation for each component that a director may ask it to create. The operations do nothing by default. A ConcreteBuilder class overrides operations for components it's interested in creating.
Here are other implementation issues to consider:
A key design issue concerns the model for the construction and assembly process. A model where the results of construction requests are simply appended to the product is usually sufficient. In the RTF example, the builder converts and appends the next token to the text it has converted so far.
But sometimes you might need access to parts of the product constructed earlier. In the Maze example we present in the Sample Code, the MazeBuilder interface lets you add a door between existing rooms. Tree structures such as parse trees that are built bottom-up are another example. In that case, the builder would return child nodes to the director, which then would pass them back to the builder to build the parent nodes.
We'll define a variant of the CreateMaze
member function
(page 84) 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
Mazethat is, the classes that define rooms, doors, and wallsand
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 () { return _currentMaze; }
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;
The RTF converter application is from ET++ [WGM88]. Its text building block uses a builder to process text stored in the RTF format.
Builder is a common pattern in Smalltalk-80 [Par90]:
The Service Configurator framework from the Adaptive Communications Environment uses a builder to construct network service components that are linked into a server at run-time [SS94]. The components are described with a configuration language that's parsed by an LALR(1) parser. The semantic actions of the parser perform operations on the builder that add information to the service component. In this case, the parser is the Director.
Abstract Factory (87) is similar to Builder in that it too may construct complex objects. The primary difference is that the Builder pattern focuses on constructing a complex object step by step. Abstract Factory's emphasis is on families of product objects (either simple or complex). Builder returns the product as a final step, but as far as the Abstract Factory pattern is concerned, the product gets returned immediately.
A Composite (163) is what the builder often builds.