Does the builder design pattern allow passing parameters? If yes, what is right way to achieve it?
Overview
The builder pattern is about hiding the details of object constructon, it places two layers of abstraction between the product and the client: builder and director
- builder is the interface for product construction, the idea is polymorphism: same methods are used to set different products
- director separates client from the building process
- Builder pattern is meant for complex products, so the builder interface could be large:
setPart1()
,setPart2()
, ...setPart100()
- director has small and simple interface:
constructProduct()
,getProduct()
- same director could use different concrete builders for different concrete products
- but also same builder interface could be used by different directors
What if some properties of the product cannot be hidden from the client.
Example problem
Assume we have the complex product such as computer set.
class ComputerSet {
private:
string procName;
string hddName;
int hddCapacity;
public:
ComputerSet(string p) : procName(p) {}
void setHdd(string name, int capacity);
};
void ComputerSet::setHdd(string name, int capacity) {
hddName = name;
hddCapacity = capacity;
}
Actually, it is not so complex. The product has only 3 parts to set: procName
, hddName
, hddCapacity
. The reason is to make example small. Let's say the computer set could have many components.
Dividing HDD part into two parts (name and capacity) has been done for the simplicity of the example. They have different nature: hddName
is builder-constant, hddCapacity
is builder-variant.
Let's assume there is the concrete product: computer set with procName="Amdel"
and hddName="Orange"
. These details could be hidden from the client. The client:
- creates the instance of the Director
- creates the instance of the AmdelBuilder
- passes AmbelBuilder to the Director
- uses Director's inteface to get the product
- does not know about "Amdel" and "Orange" strings
But there could be many HDD capacities to choose: 250, 500, 750, 1000, 1500, 2000.
One way is to create the builder for each capacity: AmdelBuilder250, AmdelBuilder500, AmdelBuilder750, AmdelBuilder1000, AmdelBuilder1500, AmdelBuilder2000.
What about designing only one AmbelBuilder
for each capacities and passing hddCapacity
parameter just before the product is created?
How to pass the parameter:
contain it inside the builder as its property (data member); two kinds of settings both from the client level
- set with constructor (once per instance)
- set with the setter (setting multiple values per single instance)
make it the argument of the builder method, it has to be passed from the director level
- make it the argument of the director construction method
- contain it inside the director as its property (data member)
First Approach
The Builder stores the hddCapacity
as own data field
class Builder {
protected:
ComputerSet *cs;
int hddCapacity;
public:
Builder(int capacity = 250) : hddCapacity(capacity) { }
virtual void createComputerSet() = 0;
virtual void setHdd() = 0;
ComputerSet* returnComputerSet() { return cs; }
};
Concrete builder's setHdd()
method does not need parameters
class AmdelBuilder : public Builder {
public:
void createComputerSet();
void setHdd();
AmdelBuilder(int capacity = 500) : Builder(capacity) { }
};
void AmdelBuilder::createComputerSet() {
cs = new ComputerSet("Amdel");
}
void AmdelBuilder::setHdd() {
cs->setHdd("Orange", hddCapacity);
}
The director does not know about the hddCapacity
parameter
class Director {
private:
Builder *builder;
public:
void setBuilder(Builder *b) { builder = b; }
void constructComputerSet();
ComputerSet* getComputerSet() { return builder->returnComputerSet(); }
};
void Director::constructComputerSet() {
builder->createComputerSet();
builder->setHdd();
}
because the client sets AmdelBuilder.hddCapacity
when the client creates the Builder
instance
Director* d = new Director();
Builder* ba = new AmdelBuilder(750); // Amdel + Orange HDD 750
d->setBuilder(ba);
d->constructComputerSet();
ComputerSet* csa750 = d->getComputerSet();
Other solution
Builder does not store hddCapacity
, but the setHdd()
method requires hddCapacity
parameter.
class Builder {
protected:
ComputerSet *cs;
public:
Builder() { }
virtual void createComputerSet() = 0;
virtual void setHdd(int hddCapacity) = 0;
ComputerSet* returnComputerSet() { return cs; }
};
The director is the direct user of the Builder's API. So the director must know the hddCapacity
value.
Question
What should is the best practice that does not violate the OO principles and the encapsulation idea of the builder pattern?