## Builder
Whenever we need to create some complex object, which may have a lot of parameters, we end up with a constructor which takes plenty of arguments. In most cases we even don't want to set up all of this, so we create dozens of constructors which allow us to set up only needed parameters. And when we add another parameter, we need to add few more constructors. That's Horrible, isn't it? So how we can solve such a problem? How to create only one Constructor and allow user to build an object by injecting only needed parameters. In previous sentence I use a word `buid`, and this is exactly a design pattern that I want to present you.
___
## Ship
This is the constructor of class `Ship`. We can easily imagine that in the future we will add at least a few more parameters.
```C++
Ship::Ship(Time* time,
std::unique_ptr> strategy,
const std::string& name,
int capacity,
int maxCrew,
int crew,
std::unique_ptr&& visitor,
int armor,
int maxArmor,
int canons,
int maxCanons,
int durability,
int maxDurability)
: _time(time),
_strategy(std::move(strategy)),
_name(name),
_capacity(capacity),
_maxCrew(maxCrew),
_crew(crew),
_visitor(std::move(visitor)),
_armor(armor),
_maxArmor(maxArmor),
_canons(canons),
_maxCanons(maxCanons)
_durability(durability),
_maxDurability(maxDurability) {
if (_capacity < 0) {
throw std::runtime_error("Capacity can't be negative value!");
}
assert(time);
_time->attach(this);
}
```
___
## Create a ship
Can you explain to me which number argument is an `armor` value, and which is a `cannon` value? Reading such code is quite hard. We can easily provide values in wrong order and instead of setting `armor` we will set a `maxCrew` or something else.
```C++
auto ship = Ship(
time,
std::move(strategy),
"Black Pearl",
200,
100,
50,
std:move(visitor),
100,
200,
20,
25,
1000,
1000);
```
___
## A new way
This is a new way of creating a ship. What do you think about this approach?
```C++
// We need to provide all mandatory parameters, the rest will have default values.
auto ship = Ship(time, name, capacity, maxCrew, maxArmor, maxCannons, maxDurability)
// Provide optional arguemnts
.setDifficultLvlStrategy(std::move(strategy))
.setCrew(100)
.setPrintVisitor(std::move(visitor))
.setArmor(100)
.setCannons(10)
.setDurability(1000)
.build();
```
___
## A better way
In previous example, we still need to provide few arguments in constructor before we will create an object. We can refactor this code and verify in `build` method if all mandatory parameters were set.
```C++
auto ship = ShipBuilder().setTime(time)
.setName("Black Pearl")
.setCapacity(500)
.setMaxCrew(200)
.setMaxArmor(1000)
.setMaxCannons(20)
.setMaxDurability(1000)
.setDifficultLvlStrategy(std::move(strategy))
.setCrew(100)
.setPrintVisitor(std::move(visitor))
.setArmor(100)
.setCannons(10)
.setDurability(1000)
.build();
```
___
## Builder class
```C++
class ShipBuilder {
public:
ShipBuilder()
: _ship(std::make_unique()) {}
[[nodiscard]] std::unique_ptr build() {
if (!_ship) {
std::cout << "Builder may be use once\n";
return nullptr;
}
// Check mandatory fields
if (!_ship->time() ||
_ship->name().empty() ||
_ship->capacity() == -1 ||
_ship->maxCrew() == -1 ||
_ship->maxArmor() == -1 ||
_ship->maxCannons() == -1 ||
_ship->maxDurability() == -1) {
std::cout << "Mandatory fields are not set!\n";
return nullptr;
}
return std::move(_ship);
}
private:
std::unique_ptr _ship;
};
```
___
## Setters
Now we need to add all setters to `ShipBuilder` class
```C++
ShipBuilder& setTime(Time* time) {
_ship->setTime(time);
return *this;
}
ShipBuilder& setStrategy(std::unique_ptr> strategy) {
_ship->setStrategy(std::move(strategy));
return *this;
}
ShipBuilder& setName(const std::string& name) {
_ship->setName(name);
return *this;
}
ShipBuilder& setCapacity(int capacity) {
_ship->setCapacity(capacity);
return *this;
}
ShipBuilder& setMaxCrew(int maxCrew) {
_ship->setMaxCrew(maxCrew);
return *this;
}
ShipBuilder& setCrew(int crew) {
_ship->setCrew(crew);
return *this;
}
ShipBuilder& setVisitor(std::unique_ptr visitor) {
_ship->setVisitor(std::move(visitor));
return *this;
}
ShipBuilder& setArmor(int armor) {
_ship->setArmor(armor);
return *this;
}
ShipBuilder& setMaxArmor(int maxArmor) {
_ship->setMaxArmor(maxArmor);
return *this;
}
ShipBuilder& setCannons(int cannons) {
_ship->setCannons(cannons);
return *this;
}
ShipBuilder& setMaxCannons(int maxCannons) {
_ship->setMaxCannons(maxCannons);
return *this;
}
ShipBuilder& setDurability(int durability) {
_ship->setDurability(durability);
return *this;
}
ShipBuilder& setMaxDurability(int maxDurability) {
_ship->setMaxDurability(maxDurability);
return *this;
}
```
___
## The ugly part
Unfortunately, if we want to follow the encapsulation rule we need to duplicate `setters` in class `Ship` we need also provide getters for all members (even if we don't use them by other classes).
```C++
class Ship {
public:
Ship& setTime(Time* time) {
_time = time;
return *this;
}
Ship& setStrategy(std::unique_ptr> strategy) {
_strategy = std::move(strategy);
return *this;
}
Ship& setName(const std::string& name) {
_name = name;
return *this;
}
Ship& setCapacity(int capacity) {
_capacity = capacity;
return *this;
}
Ship& setMaxCrew(int maxCrew) {
_maxCrew = maxCrew;
return *this;
}
Ship& setCrew(int crew) {
_crew = crew;
return *this;
}
Ship& setVisitor(std::unique_ptr visitor) {
visitor = std::move(_visitor);
return *this;
}
Ship& setArmor(int armor) {
_armor = armor;
return *this;
}
Ship& setMaxArmor(int maxArmor) {
_maxArmor = maxArmor;
return *this;
}
Ship& setCannons(int cannons) {
_canons = cannons;
return *this;
}
Ship& setMaxCannons(int maxCannons) {
_maxCannons = maxCannons;
return *this;
}
Ship& setDurability(int durability) {
_durability = durability;
return *this;
}
Ship& setMaxDurability(int maxDurability) {
_maxDurability = maxDurability;
return *this;
}
const Time* time() const { return _time; }
const std::string& name() const { return _name; }
int capacity() const { return _capacity; }
int maxCrew() const { return _maxCrew; }
int crew() const { return _crew; }
int armor() const { return _armor; }
int maxArmor() const { return _maxArmor; }
int canons() const { return _canons; }
int maxCannons() const { return _maxCannons; }
int durability() const { return _durability; }
int maxDurability() const { return _maxDurability; }
private:
// Allow to be created only by std::unique_ptr
friend std::unique_ptr std::make_unique();
Ship() = default;
Time* _time{};
std::unique_ptr> _strategy{};
std::string _name{};
int _capacity{-1};
int _maxCrew{-1};
int _crew{10};
std::unique_ptr _visitor{};
int _armor{0};
int _maxArmor{-1};
int _canons{0};
int _maxCannons{-1};
int _durability{100};
int _maxDurability{-1};
};
```
___
## Clean up
In my opinion, a better option is to declare friendship with class `ShipBuilder`. Generally, friend with other classes is not a good idea. But in builder pattern we have a lot of profits:
* Don't need to duplicate setters
* Some of the fields should not be changed after Ship will be built, but still we need to have a setter for this field
* We don't need to return a reference to `Ship` for all setters.
* Don't need to have getters for all members
___
```C++
class Ship {
public:
void setCrew(int crew) { _crew = crew; }
void setArmor(int armor) { _armor = armor; }
void setCannons(int cannons) { _canons = cannons; }
void setDurability(int durability) { _durability = durability; }
const std::string& name() const { return _name; }
int capacity() const { return _capacity; }
int crew() const { return _crew; }
int armor() const { return _armor; }
int canons() const { return _canons; }
int durability() const { return _durability; }
private:
// Allow to be created only by std::unique_ptr
friend std::unique_ptr std::make_unique();
Ship() = default;
}
```
___
```C++
class ShipBuilder {
public:
ShipBuilder()
: _ship(std::make_unique()) {}
[[nodiscard]] std::unique_ptr build() {
if (!_ship) {
std::cout << "Builder may be use once\n";
return nullptr;
}
// Check mandatory fields
if (!_ship->_time ||
_ship->_name.empty() ||
_ship->_capacity == -1 ||
_ship->_maxCrew == -1 ||
_ship->_maxArmor == -1 ||
_ship->_maxCannons == -1 ||
_ship->_maxDurability == -1) {
std::cout << "Mandatory fields are not set!\n";
return nullptr;
}
return std::move(_ship);
}
ShipBuilder& setTime(Time* time) {
_ship->_time = time;
return *this;
}
ShipBuilder& setStrategy(std::unique_ptr> strategy) {
_ship->_strategy = std::move(strategy);
return *this;
}
ShipBuilder& setName(const std::string& name) {
_ship->_name = name;
return *this;
}
ShipBuilder& setCapacity(int capacity) {
_ship->_capacity = capacity;
return *this;
}
ShipBuilder& setMaxCrew(int maxCrew) {
_ship->_maxCrew = maxCrew;
return *this;
}
ShipBuilder& setCrew(int crew) {
_ship->_crew = crew;
return *this;
}
ShipBuilder& setVisitor(std::unique_ptr visitor) {
_ship->_visitor = std::move(visitor);
return *this;
}
ShipBuilder& setArmor(int armor) {
_ship->_armor = armor;
return *this;
}
ShipBuilder& setMaxArmor(int maxArmor) {
_ship->_maxArmor = maxArmor;
return *this;
}
ShipBuilder& setCannons(int cannons) {
_ship->_canons = cannons;
return *this;
}
ShipBuilder& setMaxCannons(int maxCannons) {
_ship->_maxCannons = maxCannons;
return *this;
}
ShipBuilder& setDurability(int durability) {
_ship->_durability = durability;
return *this;
}
ShipBuilder& setMaxDurability(int maxDurability) {
_ship->_maxDurability = maxDurability;
return *this;
}
private:
std::unique_ptr _ship;
};
```
___
## Usage
For both implementations, we use builder in the same way:
```C++
int main() {
auto ship = ShipBuilder().build();
if (ship) {
std::cout << "Ship name: " << ship->name() << '\n';
}
Time time;
auto ship2 = ShipBuilder()
.setTime(&time)
.setName("Black Pearl")
.setCapacity(500)
.setMaxCrew(200)
.setMaxArmor(1000)
.setMaxCannons(20)
.setMaxDurability(1000)
.build();
if (ship2) {
std::cout << "Ship name: " << ship2->name() << '\n';
}
}
```
```bash
Mandatory fields are not set!
Ship name: Black Pearl
```
___
## gRPC
A perfect example of builder is an gRPC library. We use the builder pattern to create a server object, which we can customize. Based on provided arguments we can create synchronous or asynchronous server, with or without compression, with one or more services, with additional options like max response size etc...
```C++
grpc::Status status;
std::shared_ptr provider =
grpc::FileWatcherAuthorizationPolicyProvider::Create(
authz_policy_path, /*refresh_interval_sec=*/3600, &status);
auto option = std::make_unqiue();
grpc::ChannelArguments args;
args.SetMaxReceiveMessageSize(4096);
args.SetMaxSendMessageSize(4096);
option->UpdateArguments(args);
std::unique_ptr server =
grpc::ServerBuilder().AddListeningPort("127.0.0.1:10000", InsecureServerCredentials())
.RegisterService(&service1)
.RegisterService("127.0.0.1:9999", &service2)
.SetAuthorizationPolicyProvider(provider)
.SetOption(std::move(option))
.SetDefaultCompressionLevel(grpc::GRPC_COMPRESS_LEVEL_HIGH)
.BuildAndStart();
```
___
## DRY
At the beginning, we decided to have 3 types of ships. We always need to remember which value to pass to each parameter to create a specific type of ship. If we decide that something is not okay with balance, we need to change this parameter everywhere. If we forget to change it in some code, we end up with different parameters for the same ship type. We can of course decided to have some global variable, but this is an ugly solution. **We should wrap the whole logic in one place**. To make it more flexible, we will describe all parameters in a JSON file, so we will not be forced to recompile the whole binary every time we change the ship parameters. Because we don't want to modify existing code, we just create a new class that inherit `private` from the `ShipBuilder` so we can't use the interface `ShipBuilder` outside, but still `ShipJsonBuilder` may use this implementation.
```C++
class ShipJsonBuilder : private ShipBuilder {
public:
ShipJsonBuilder(const std::filesystem::path& path) { parseFile(path); }
std::unique_ptr buildBrig(Time* time, const std::string& name) const {
return setTime(time)
.setName(name)
.setCapacity(_shipsData["brig"]["capacity"])
.setMaxCrew(_shipsData["brig"]["maxCrew"])
.setMaxArmor(_shipsData["brig"]["maxArmor"])
.setMaxCannons(_shipsData["brig"]["maxCannons"])
.setMaxDurability(_shipsData["brig"]["maxDurability"])
.build();
}
std::unique_ptr buildFrigate(Time* time, const std::string& name) const;
std::unique_ptr buildGaleon(Time* time, const std::string& name) const;
private:
json _shipsData;
void parseFile(const std::filesystem::path& path); // Parse file and store data in _shipsData
};
```
___
## Usage
Now, we can easily create one of existing types of ship, without worry about which values to pass. What's more, we read the file from JSON, so we decouple values from the binary, so every time we make some changes in balance, we don't need to recompile any single file.
```C++
int main() {
Time time;
auto ship1 = ShipJsonBuilder().buildGaleon(&time, "Queens Anne Revenge");
if (ship1) {
std::cout << "Ship name: " << ship1->name() << '\n';
}
auto ship2 = ShipJsonBuilder().buildFrigate(&time, "Black Pearl");
if (ship2) {
std::cout << "Ship name: " << ship2->name() << '\n';
}
auto ship3 = ShipJsonBuilder().buildBrig(&time, "Black Widow");
if (ship3) {
std::cout << "Ship name: " << ship3->name() << '\n';
}
}
```
```bash
Ship name: Queens Anne Revenge
Ship name: Black Pearl
Ship name: Black Widow
```
___
## Different builder
But there is even more! Currently, we focus only on one builder, which always create a `Ship` class. We also create a `ShipJsonBuilder` which reuse implementation from `ShipBuilder` and allow to user read default parameters from the file, so we always create a `Ship` of the same type with equal parameters. We also saw that gRPC use the same logic to also user create various type of server. Furthermore, gRPC add plenty of methods which allow us to set parameters of this server. We can even go further and create a few types of builder, and then pass a pointer to the base class to `Driector` and create a different object.
```C++
class CrewBuilder {
public:
CrewBuilder& setName(const std::string& name) = 0;
CrewBuilder& setWeapon(std::unique_ptr&& weapon) = 0;
CrewBuilder& setHp(Hp hp) = 0;
std::unique_ptr build() = 0;
};
```
```C++
class PirateBuilder : public CrewBuilder {
public:
CrewBuilder& setName(const std::string& name) override { /* some implementation */ }
CrewBuilder& setWeapon(std::unique_ptr&& weapon) override { /* some implementation */ }
CrewBuilder& setHp(Hp hp) override { /* some implementation */ }
std::unique_ptr build() { return std::make_unique(/* args */); }
};
```
___
## Tavern
Based on which tavern we are, we can recruit, for instance:
* the marine,
* the volunteer,
* the pirate
Each kind of Sailor will act differently in combat and during sail. They also have a different pay. We don't want to hard-code a type of Tavern on every island, because during a game, we can for instance conquer an island and free it forms pirates, so we will no longer recruit their pirates, but maybe a marine or volunteer. That's why we should pass a builder class as an argument to tavern, so we can easily swap it during a game.
```C++
class Tawern {
public:
Tawern(std::unique_ptr&& builder): _builder(std::move(builder)) {}
std::unique_ptr recruit(const std::string& name, std::unique_ptr&& weapon) {
return _builder->setName(name).setWeapon(weapon).setHp(40).build();
}
private:
std::unique_ptr _builder;
};
```
___
## Mixing two design patterns
If you have a feeling that this looks similar to factory method, you are right! Both design patterns are similar. There are few main differences:
* Builder focuses on constructing a complex object step by step. Abstract Factory emphasizes a family of product objects (either simple or complex). Builder returns the product as a final step, but as far as the Abstract Factory is concerned, the product gets returned immediately.
* Builder often builds a Composite.
* Often, designs start out using Factory Method (less complicated, more customizable, subclasses proliferate) and evolve toward Abstract Factory, Prototype, or Builder (more flexible, more complex) as the designer discovers where more flexibility is needed.
* Sometimes creational patterns are complementary: Builder can use one of the other patterns to implement which components get built. Abstract Factory, Builder, and Prototype can use Singleton in their implementations.
___
## Exercise 1
* Go to the directory Ship and create class ShipBuilder. The mandatory fields are:
* name
* capacity
* time
* The non-mandatory fields are:
* difficultStrategy -> default value should be set as Easy
* crew -> default value should be set as 10
* durability -> default value should be set as 1000
* armor -> default value should be set as 0
* PrintVisitor -> default value should be set as Pretty Print
* Try to build your own Ship!
___
## Exercise 2
* Go to the directory Ship and finish implementation of class ShipJsonBuilder. You should allow building 3 types of ship:
* Brig
* Frigate
* Galoen
* If you want to read a JSON filed, just use operator[] to get any of nested field, example: _shipsData["brige"]["armor"]
* Try to build a ship using a new builder.
* Try to change some values in ships.json and run binary without recompilation. You should see new values!
___
## Factory method
Because we saw in the previous example a factory method patter, I want to tell a little more about it. Whenever we want to hide implementation details about creation of new object (and also a final type) we should use factory method.
___
Interface of factory:
```C++
class Factory {
public:
virtual std::unique_ptr create(const std::string& name, std::unique_ptr&& weapon, int hp) = 0;
};
```
Implementations:
```C++
class PirateBuilder : public CrewBuilder {
public:
std::unique_ptr create(const std::string& name, std::unique_ptr&& weapon, int hp) override {
return std::make_unique(name, std::move(weapon), hp);
}
};
class MarineBuilder : public CrewBuilder {
public:
std::unique_ptr create(const std::string& name, std::unique_ptr&& weapon, int hp) override {
return std::make_unique(name, std::move(weapon), hp);
}
};
```
Class that use factory to create object:
```C++
class Tawern {
public:
Tawern(std::unique_ptr&& factory): _factory(std::move(factory)) {}
std::unique_ptr recruit(const std::string& name, std::unique_ptr&& weapon) {
return _factory->create(name, std::move(weapon), 40);
}
private:
std::unique_ptr _factory_;
};
```
___
## Exercise 3
* Go to the directory Ship and create abstract class FruitFactory.
* Create FruitFactory
* Create ItemFactory
* Create AlcoholFactory
* Test in main.cpp if you can create any type of cargo
* How to handle scenario, when different cargo takes other arguments?
___
## Different arguments
At the end, I want to talk about different arguments in constructors. We have three derived class from `Cargo`, each class takes different arguments.
```C++
struct Fruit : public Cargo {
int rottenCounter;
Fruit(size_t amount, int rottenCounter);
size_t getPrice() const override;
const std::string& name() const override;
void accept(const CargoDamageVisitor& visitor) override;
};
struct Item : public Cargo {
enum class Type { Common, Rare, Epic, Legendary };
Type type;
Item(size_t amount, Type type);
size_t getPrice() const override;
const std::string& name() const override;
void accept(const CargoDamageVisitor& visitor) override;
};
struct Alcohol : public Cargo {
enum class Type { White, Spiced, Dark};
int power;
Type type;
Alcohol(size_t amount, int power, Type type);
size_t getPrice() const override;
const std::string& name() const override;
void accept(const CargoDamageVisitor& visitor) override;
};
```
___
## How to solve it?
**Of course by using template!**
```C++
template
class CargoFactory {
public:
virtual std::unique_ptr create(size_t amount, Args... args) = 0;
};
```
```C++
class FruitFactory : public CargoFactory {
public:
std::unique_ptr create(size_t amount, int rottenCounter) override {
return std::make_unique(amount, rottenCounter);
}
};
class AlcoholFactory : public CargoFactory {
public:
std::unique_ptr create(size_t amount, int power, Alcohol::Type type) override {
return std::make_unique(amount, power, type);
}
};
class ItemFactory : public CargoFactory {
public:
std::unique_ptr create(size_t amount, Item::Type type) override {
return std::make_unique- (amount, type);
}
};
```