Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |
The goal of Builder (which is a Creational pattern, like the
Factories we ve just looked at) is to separate the construction of an object
from its representation. This means that the construction process stays the
same, but the resulting object has different possible representations. GoF
points out that the main difference between Builder and Abstract Factory is
that a Builder creates the object step-by-step, so the fact that the creation
process is spread out in time seems to be important. In addition, the
director gets a stream of pieces that it passes to the Builder, and each
piece is used to perform one of the steps in the build process.
The following example models a bicycle that can have a
choice of parts, according to its type (mountain bike, touring bike, or racing
bike). A Builder class is associated with each type of bicycle, and each Builder
implements the interface specified in the abstract class BicycleBuilder.
A separate class, BicycleTechnician, represents the director object
described in GoF, and uses a concrete BicycleBuilder object to construct
a Bicycle object.
//: C10:Bicycle.h
// Defines classes to build bicycles;
// Illustrates the Builder design pattern.
#ifndef BICYCLE_H
#define BICYCLE_H
#include <iostream>
#include <string>
#include <vector>
#include <cstddef>
#include "../purge.h"
using std::size_t;
class BicyclePart {
public:
enum BPart { FRAME, WHEEL, SEAT, DERAILLEUR,
HANDLEBAR, SPROCKET, RACK, SHOCK, NPARTS };
private:
BPart id;
static std::string names[NPARTS];
public:
BicyclePart(BPart bp) { id = bp; }
friend std::ostream&
operator<<(std::ostream& os, const
BicyclePart& bp) {
return os << bp.names[bp.id];
}
};
class Bicycle {
std::vector<BicyclePart*> parts;
public:
~Bicycle() { purge(parts); }
void addPart(BicyclePart* bp) { parts.push_back(bp);
}
friend std::ostream&
operator<<(std::ostream& os, const
Bicycle& b) {
os << "{ ";
for(size_t i = 0; i < b.parts.size(); ++i)
os << *b.parts[i] << '
';
return os << '}';
}
};
class BicycleBuilder {
protected:
Bicycle* product;
public:
BicycleBuilder() { product = 0; }
void createProduct() { product = new Bicycle; }
virtual void buildFrame() = 0;
virtual void buildWheel() = 0;
virtual void buildSeat() = 0;
virtual void buildDerailleur() = 0;
virtual void buildHandlebar() = 0;
virtual void buildSprocket() = 0;
virtual void buildRack() = 0;
virtual void buildShock() = 0;
virtual std::string getBikeName() const = 0;
Bicycle* getProduct() {
Bicycle* temp = product;
product = 0; // Relinquish product
return temp;
}
};
class MountainBikeBuilder : public BicycleBuilder {
public:
void buildFrame();
void buildWheel();
void buildSeat();
void buildDerailleur();
void buildHandlebar();
void buildSprocket();
void buildRack();
void buildShock();
std::string getBikeName() const { return
"MountainBike";}
};
class TouringBikeBuilder : public BicycleBuilder {
public:
void buildFrame();
void buildWheel();
void buildSeat();
void buildDerailleur();
void buildHandlebar();
void buildSprocket();
void buildRack();
void buildShock();
std::string getBikeName() const { return
"TouringBike"; }
};
class RacingBikeBuilder : public BicycleBuilder {
public:
void buildFrame();
void buildWheel();
void buildSeat();
void buildDerailleur();
void buildHandlebar();
void buildSprocket();
void buildRack();
void buildShock();
std::string getBikeName() const { return
"RacingBike"; }
};
class BicycleTechnician {
BicycleBuilder* builder;
public:
BicycleTechnician() { builder = 0; }
void setBuilder(BicycleBuilder* b) { builder = b; }
void construct();
};
#endif // BICYCLE_H ///:~
A Bicycle holds a vector of pointers to BicyclePart,
representing the parts used to construct the bicycle. To initiate the construction
of a bicycle, a BicycleTechnician (the director in this example) calls
BicycleBuilder::createproduct( ) on a derived BicycleBuilder
object. The BicycleTechnician::construct( ) function calls all the
functions in the BicycleBuilder interface (since it doesn t know what
type of concrete builder it has). The concrete builder classes omit (via empty
function bodies) those actions that do not apply to the type of bicycle they
build, as you can see in the following implementation file:
//: C10:Bicycle.cpp {O} {-mwcc}
#include "Bicycle.h"
#include <cassert>
#include <cstddef>
using namespace std;
std::string BicyclePart::names[NPARTS] = {
"Frame", "Wheel",
"Seat", "Derailleur",
"Handlebar", "Sprocket",
"Rack", "Shock" };
// MountainBikeBuilder implementation
void MountainBikeBuilder::buildFrame() {
product->addPart(new
BicyclePart(BicyclePart::FRAME));
}
void MountainBikeBuilder::buildWheel() {
product->addPart(new
BicyclePart(BicyclePart::WHEEL));
}
void MountainBikeBuilder::buildSeat() {
product->addPart(new
BicyclePart(BicyclePart::SEAT));
}
void MountainBikeBuilder::buildDerailleur() {
product->addPart(
new BicyclePart(BicyclePart::DERAILLEUR));
}
void MountainBikeBuilder::buildHandlebar() {
product->addPart(
new BicyclePart(BicyclePart::HANDLEBAR));
}
void MountainBikeBuilder::buildSprocket() {
product->addPart(new
BicyclePart(BicyclePart::SPROCKET));
}
void MountainBikeBuilder::buildRack() {}
void MountainBikeBuilder::buildShock() {
product->addPart(new BicyclePart(BicyclePart::SHOCK));
}
// TouringBikeBuilder implementation
void TouringBikeBuilder::buildFrame() {
product->addPart(new
BicyclePart(BicyclePart::FRAME));
}
void TouringBikeBuilder::buildWheel() {
product->addPart(new
BicyclePart(BicyclePart::WHEEL));
}
void TouringBikeBuilder::buildSeat() {
product->addPart(new
BicyclePart(BicyclePart::SEAT));
}
void TouringBikeBuilder::buildDerailleur() {
product->addPart(
new BicyclePart(BicyclePart::DERAILLEUR));
}
void TouringBikeBuilder::buildHandlebar() {
product->addPart(
new BicyclePart(BicyclePart::HANDLEBAR));
}
void TouringBikeBuilder::buildSprocket() {
product->addPart(new
BicyclePart(BicyclePart::SPROCKET));
}
void TouringBikeBuilder::buildRack() {
product->addPart(new BicyclePart(BicyclePart::RACK));
}
void TouringBikeBuilder::buildShock() {}
// RacingBikeBuilder implementation
void RacingBikeBuilder::buildFrame() {
product->addPart(new
BicyclePart(BicyclePart::FRAME));
}
void RacingBikeBuilder::buildWheel() {
product->addPart(new BicyclePart(BicyclePart::WHEEL));
}
void RacingBikeBuilder::buildSeat() {
product->addPart(new
BicyclePart(BicyclePart::SEAT));
}
void RacingBikeBuilder::buildDerailleur() {}
void RacingBikeBuilder::buildHandlebar() {
product->addPart(
new BicyclePart(BicyclePart::HANDLEBAR));
}
void RacingBikeBuilder::buildSprocket() {
product->addPart(new
BicyclePart(BicyclePart::SPROCKET));
}
void RacingBikeBuilder::buildRack() {}
void RacingBikeBuilder::buildShock() {}
// BicycleTechnician implementation
void BicycleTechnician::construct() {
assert(builder);
builder->createProduct();
builder->buildFrame();
builder->buildWheel();
builder->buildSeat();
builder->buildDerailleur();
builder->buildHandlebar();
builder->buildSprocket();
builder->buildRack();
builder->buildShock();
} ///:~
The Bicycle stream inserter calls the corresponding
inserter for each BicyclePart, and that prints its type name so that you
can see what a Bicycle contains. Here is a sample program:
//: C10:BuildBicycles.cpp
//{L} Bicycle
// The Builder design pattern.
#include <cstddef>
#include <iostream>
#include <map>
#include <vector>
#include "Bicycle.h"
#include "../purge.h"
using namespace std;
// Constructs a bike via a concrete builder
Bicycle* buildMeABike(
BicycleTechnician& t, BicycleBuilder* builder) {
t.setBuilder(builder);
t.construct();
Bicycle* b = builder->getProduct();
cout << "Built a " <<
builder->getBikeName() << endl;
return b;
}
int main() {
// Create an order for some bicycles
map <string, size_t> order;
order["mountain"] = 2;
order["touring"] = 1;
order["racing"] = 3;
// Build bikes
vector<Bicycle*> bikes;
BicycleBuilder* m = new MountainBikeBuilder;
BicycleBuilder* t = new TouringBikeBuilder;
BicycleBuilder* r = new RacingBikeBuilder;
BicycleTechnician tech;
map<string, size_t>::iterator it =
order.begin();
while(it != order.end()) {
BicycleBuilder* builder;
if(it->first == "mountain")
builder = m;
else if(it->first == "touring")
builder = t;
else if(it->first == "racing")
builder = r;
for(size_t i = 0; i < it->second; ++i)
bikes.push_back(buildMeABike(tech, builder));
++it;
}
delete m;
delete t;
delete r;
// Display inventory
for(size_t i = 0; i < bikes.size(); ++i)
cout << "Bicycle: " <<
*bikes[i] << endl;
purge(bikes);
}
/* Output:
Built a MountainBike
Built a MountainBike
Built a RacingBike
Built a RacingBike
Built a RacingBike
Built a TouringBike
Bicycle: {
Frame Wheel Seat Derailleur Handlebar Sprocket Shock
}
Bicycle: {
Frame Wheel Seat Derailleur Handlebar Sprocket Shock
}
Bicycle: { Frame Wheel Seat Handlebar Sprocket }
Bicycle: { Frame Wheel Seat Handlebar Sprocket }
Bicycle: { Frame Wheel Seat Handlebar Sprocket }
Bicycle: {
Frame Wheel Seat Derailleur Handlebar Sprocket Rack }
*/ ///:~
The power of this pattern is that it separates the algorithm
for assembling parts into a complete product from the parts themselves and
allows different algorithms for different products via different implementations
of a common interface.
Thinking in C++ Vol 2 - Practical Programming |
Prev |
Home |
Next |