We start with this code:
#include <iostream>
struct BuildingBlock {
virtual std::string name() = 0;
virtual ~BuildingBlock() = default;
};
struct LegoBrick : BuildingBlock {
LegoBrick() {};
std::string name() override {
return "LegoBrick";
}
};
struct LegoFactory {
static BuildingBlock* produce_part() {
auto brick = new LegoBrick();
return brick;
}
};
struct BlueBrixxBrick : BuildingBlock {
std::string name() override {
return "BlueBrixxBrick";
}
};
struct BlueBrixxFactory {
static BuildingBlock* produce_part() {
return new BlueBrixxBrick();
}
};
int main() {
BuildingBlock* brick1 = LegoFactory::produce_part();
BuildingBlock* brick2 = BlueBrixxFactory::produce_part();
std::cout << brick1->name() << std::endl;
std::cout << brick2->name() << std::endl;
}
There are two factories. One factory is a LEGO factory that produces LEGO bricks. The other one is a factory that produces BlueBrixx bricks.
We could now change the code of the BlueBrixxFactory in a way that it produces LEGO bricks instead of BlueBrixx bricks:
struct BlueBrixxFactory {
static BuildingBlock* produce_part() {
return new LegoBrick();
}
};
As you might know, LEGO is very pedantic when it comes to the use of their product name. They will sue everyone you is going to use thier product name. A question that arises is: who can we enforce that only the LEGO factory can produce LEGO bricks, and no one else?
We make the constructor of LegoBrick private. This will do the job,
but this will also lead to the problem that no one can create an instance of LegoBrick.
To enable the LegoFactory again, the constructor of LegoBrick needs to becomae a friend of LegoFactory:
#include <iostream>
struct BuildingBlock {
virtual std::string name() = 0;
virtual ~BuildingBlock() = default;
};
class LegoFactory;
struct LegoBrick : BuildingBlock {
private:
LegoBrick() {};
public:
std::string name() override {
return "LegoBrick";
}
friend LegoFactory;
};
struct LegoFactory {
static BuildingBlock* produce_part() {
auto brick = new LegoBrick();
return brick;
}
};
struct BlueBrixxBrick : BuildingBlock {
std::string name() override {
return "BlueBrixxBrick";
}
};
struct BlueBrixxFactory {
static BuildingBlock* produce_part() {
//return new LegoBrick(); // would give an error if commented out
return new BlueBrixxBrick();
}
};
int main() {
BuildingBlock* brick1 = LegoFactory::produce_part();
BuildingBlock* brick2 = BlueBrixxFactory::produce_part();
std::cout << brick1->name() << std::endl;
std::cout << brick2->name() << std::endl;
}
Now only the LegoFactory can produce instances of LegoBrick.
Imagine a LegoBrick would have some secret value:
struct LegoBrick : BuildingBlock {
private:
LegoBrick() {};
int secret_value = 42;
public:
std::string name() override {
return "LegoBrick";
}
friend LegoFactory;
};
Since LegoFactory is a friend of LegoBrick, LegoFactory has now full access to the private variables of LegoBrick.
In other words, LegoFactory can do bad things with LegoBrick.
The initial goal was that LegoFactory is the only factory that can create LegoBrick, but the intention was not that a LegoFactory has now free full exclusive access to everything a LegoBrick has to hide:
#include <iostream>
struct BuildingBlock {
virtual std::string name() = 0;
virtual ~BuildingBlock() = default;
};
class LegoFactory;
struct LegoBrick : BuildingBlock {
private:
LegoBrick() {};
int secret_value = 42;
public:
std::string name() override {
return "LegoBrick";
}
friend LegoFactory;
};
struct LegoFactory {
static BuildingBlock* produce_part() {
auto brick = new LegoBrick();
std::cout << "secret: " << brick->secret_value << std::endl;
return brick;
}
};
struct BlueBrixxBrick : BuildingBlock {
std::string name() override {
return "BlueBrixxBrick";
}
};
struct BlueBrixxFactory {
static BuildingBlock* produce_part() {
//return new LegoBrick(); // would give an error if commented out
return new BlueBrixxBrick();
}
};
int main() {
BuildingBlock* brick1 = LegoFactory::produce_part();
BuildingBlock* brick2 = BlueBrixxFactory::produce_part();
std::cout << brick1->name() << std::endl;
std::cout << brick2->name() << std::endl;
}
Here comes the passkey idiom:
#include <iostream>
struct BuildingBlock {
virtual std::string name() = 0;
virtual ~BuildingBlock() = default;
};
class LegoFactory;
class PassKey {
private:
PassKey() = default;
friend LegoFactory;
};
struct LegoBrick : BuildingBlock {
public:
LegoBrick(PassKey& key) {};
std::string name() override {
return "LegoBrick";
}
private:
int secret_value = 42;
};
struct LegoFactory {
static BuildingBlock* produce_part() {
auto key = PassKey();
auto brick = new LegoBrick(key);
//std::cout << "secret: " << brick->secret_value << std::endl; // will lead to an error
return brick;
}
};
struct BlueBrixxBrick : BuildingBlock {
std::string name() override {
return "BlueBrixxBrick";
}
};
struct BlueBrixxFactory {
static BuildingBlock* produce_part() {
//return new LegoBrick(); // would give an error if commented out
return new BlueBrixxBrick();
}
};
int main() {
BuildingBlock* brick1 = LegoFactory::produce_part();
BuildingBlock* brick2 = BlueBrixxFactory::produce_part();
std::cout << brick1->name() << std::endl;
std::cout << brick2->name() << std::endl;
}
The constructor of LegoBrick is now public again,
but its ctor expects a PassKey.
This PassKey can only be created by the LegoFactory,
but does not leak any information of a LegoBrick.
This way, the LegoFactory is the only factory that can produce
instances of LegoBrick.
At the same time, we do not have the issue of leaking any internal of
LegoBrick.
To roll out this also to the BlueBrixxFactory, i.e., only the BlueBrixxFactory should be able to create instances of BlueBrixxBrick. We can change PassKey into a template:
template<typename T>
class PassKey {
explicit PassKey() = default;
friend T;
};
Which result in the following code:
#include <iostream>
struct BuildingBlock {
virtual std::string name() = 0;
virtual ~BuildingBlock() = default;
};
template<typename T>
class PassKey {
explicit PassKey() = default;
friend T;
};
class LegoFactory;
struct LegoBrick : BuildingBlock {
public:
explicit LegoBrick(PassKey<LegoFactory>& key) {};
std::string name() override {
return "LegoBrick";
}
private:
int secret_value = 42;
};
struct LegoFactory {
static BuildingBlock* produce_part() {
auto key = PassKey<LegoFactory>();
auto brick = new LegoBrick(key);
return brick;
}
};
struct BlueBrixxFactory;
struct BlueBrixxBrick : BuildingBlock {
explicit BlueBrixxBrick(PassKey<BlueBrixxFactory>& key) {}
std::string name() override {
return "BlueBrixxBrick";
}
};
struct BlueBrixxFactory {
static BuildingBlock* produce_part() {
auto key = PassKey<BlueBrixxFactory>();
return new BlueBrixxBrick(key);
}
};
int main() {
BuildingBlock* brick1 = LegoFactory::produce_part();
BuildingBlock* brick2 = BlueBrixxFactory::produce_part();
std::cout << brick1->name() << std::endl;
std::cout << brick2->name() << std::endl;
}