Design Patterns – Passing Parameters in Builder Design Pattern

design-patterns

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?

Best Answer

I believe neither of your approaches violate anything and both can be used just fine.

Passing parameters to the builder can be done either using constructor or setter methods. I do not see any problem with it.

I tend to pass parameters via constructor if there are not so many of them. If I have more than 3-5 configuration parameters I switch to using methods to configure Builder.