Creational Patterns (🛠 Second) - draft
Patterns that deal with object creation
1. Singleton
What is Singleton Pattern?
Start from I need a system where there is a
single
object that is accessible from anywhere in the code - how do I do this without global variables? - In other word, Singleton is a globally accessible class where we guarantee only a single instance is created.
Let's see the code
class singleton
{
public:
static singleton& get_instance()
{
static singleton instance; // Lazy Initialization is used in Singleton
return instance; // Instantiated on first use.
}
private:
int value;
singleton() = default; //private constructor
public:
singleton(singleton const&)= delete; //deleting copy constructor
void operator=(singleton const&)= delete; //deleting assignment operator
int get_value() {
return value++;
}
};
So, As you can see in this pattern,
- Instantiates the object on its first use
- Ideally hides a private constructor
-
Reveals a public get_instance function that returns a reference to a
static
instance of the class - Provides "global" access to a single object
2. Factory / Factory Method
What is the Factory / Factory Method Pattern?
In some articles & stackoverflows, mention that Factory and Factory Method is slightly different, but trying to explain in the same category.
When you want to separate creation code from client code.
When you want a client or service to only depend on a base class for object creation and not each concrete class
Let's check the UML of Factory Pattern
Just like above UML, We can create the "ConcreteProductA and B" with the AbstractFactory.
Base on this, lets see the code actually trying to solve the real world problem.
/*
Forum class - ForumWebsite
*/
struct Forum {
string name;
vector<User*> users;
Forum(string name) : name(name) {}
UserFactory* register_user() {
cout << "Welcome! Do you want to make an account or continue as a guest?" << endl;
cout << "1. Make Account" << endl;
cout << "2. Continue as Guest" << endl;
int choice = 0;
cout << "Enter your choice (1-2)" << endl;
cin >> choice;
UserFactory *factory_class;
if(choice == 1)
factory_class = new MemberUserFactory;
else
factory_class = new GuestUserFactory;
return factory_class;
}
void add_user() {
UserFactory *factory = register_user(); //get a member or guest factory
users.push_back(factory->create_user());
delete factory;
}
~Forum() {
for (User *u : users)
{
delete u;
}
}
};
UserFactory, GuestFactory, MemberFactory
struct UserFactory {
virtual User* create_user() = 0;
};
struct GuestUserFactory : UserFactory {
User* create_user() override {
return new Guest("","");
}
};
struct MemberUserFactory : UserFactory {
User* create_user() override {
string username;
string password;
cout << "Enter username: " << endl;
cin >> username;
cout << "Enter password: " << endl;
cin >> password;
return new Member(username, password);
}
};
User Abstract class, Member, Guest
enum Permission {
NONE,
READ,
LIKE,
SHARE,
FLAG,
WRITE
};
struct User {
string username;
string password;
vector<Permission> permissions;
string date_joined;
User(string username, string password) : username(username), password(password) {
// declaring argument of time()
time_t my_time = time(NULL);
date_joined = ctime(&my_time);
}
bool authenticate(const string& password_input) {
return password_input == password;
}
pair<string, string> get_profile() {
return make_pair(username, date_joined);
}
virtual string getString() const {
return "Username: ";
}
friend ostream& operator<<(ostream& os, const User& u)
{
os << u.getString() << u.username << " Date joined: " << u.date_joined << endl;
return os;
}
};
struct Guest : User {
Guest(string username, string password) : User(username, password) {
permissions.push_back(Permission::READ);
permissions.push_back(Permission::LIKE);
permissions.push_back(Permission::SHARE);
permissions.push_back(Permission::FLAG);
}
string getString() const override {
return "Guest User: ";
}
};
struct Member : User {
Member(string username, string password) : User(username, password) {
permissions.push_back(Permission::READ);
permissions.push_back(Permission::LIKE);
permissions.push_back(Permission::SHARE);
permissions.push_back(Permission::FLAG);
permissions.push_back(Permission::WRITE);
}
string getString() const override {
return "Member User: ";
}
};
As we can see, these codes provide a separate interface for object creation where each subclass can alter the type of object created.
If we want to add a new product, just create a new Factory and override the creation method in it.
3. Abstract Factory
What is Abstract Factory Design Pattern?
Creational Design Pattern that lets you produce families of related objects without specifying their concrete classes
Let's check the UML of Abstract Factory Pattern
Looks very similar with Factory Pattern (Obviously) - and as we can see this pattern offers the interface for creating a family of related objects, without explicitly specifying their classes - factory for factories
Base on the UML, lets check code
AbstractProductA, B
/**
* AbstractProductA
*/
class AbstractProductA {
public:
virtual ~AbstractProductA() {}
virtual string UsefulFunctionA() const = 0;
};
/**
* AbstractProductB
*/
class AbstractProductB {
public:
virtual ~AbstractProductB() {}
virtual string UsefulFunctionB() const = 0;
virtual string AnotherUsefulFunctionB(const AbstractProductA& collaborator) const = 0;
};
class ConcreteProductB1 : public AbstractProductB {
public:
string UsefulFunctionB() const override {
return "ConcreteProductB1";
}
string AnotherUsefulFunctionB(const AbstractProductA& collaborator) const override {
const string result = collaborator.UsefulFunctionA();
return "The result of the B1 collaborating with ( " + result + " )";
}
};
ConcreteA1, A2, B1, B2
class ConcreteProductA1 : public AbstractProductA {
public:
string UsefulFunctionA() const override {
return "ConcreteProductA1";
};
};
class ConcreteProductA2 : public AbstractProductA {
public:
string UsefulFunctionA() const override {
return "ConcreteProductA2";
}
};
class ConcreteProductB1 : public AbstractProductB {
public:
string UsefulFunctionB() const override {
return "ConcreteProductB1";
}
string AnotherUsefulFunctionB(const AbstractProductA& collaborator) const override {
const string result = collaborator.UsefulFunctionA();
return "The result of the B1 collaborating with ( " + result + " )";
}
};
class ConcreteProductB2 :public AbstractProductB {
public:
string UsefulFunctionB() const override {
return "ConcreteProductB2";
}
string AnotherUsefulFunctionB(const AbstractProductA& collaborator) const override {
const string result = collaborator.UsefulFunctionA();
return "The result of the B2 collaborating with ( " + result + " )";
}
};
Abstract Factory (interface)
class AbstractFactory {
public:
virtual AbstractProductA* CreateProductA() const = 0;
virtual AbstractProductB* CreateProductB() const = 0;
};
ConcreteFactory1, 2
class ConcreteFactory1 : public AbstractFactory {
public:
AbstractProductA* CreateProductA() const override {
return new ConcreteProductA1();
}
AbstractProductB* CreateProductB() const override {
return new ConcreteProductB1();
}
};
class ConcreteFactory2 : public AbstractFactory {
public:
AbstractProductA* CreateProductA() const override {
return new ConcreteProductA2();
}
AbstractProductB* CreateProductB() const override {
return new ConcreteProductB2();
}
};
Client code
void ClientCode(const AbstractFactory& factory) {
AbstractProductA* productA = factory.CreateProductA();
AbstractProductB* productB = factory.CreateProductB();
cout << productA -> UsefulFunctionA() << endl;
cout << productB -> AnotherUsefulFunctionB(*productA) << endl;
delete productA;
delete productB;
}
At last, in main
ConcreteFactory1* factory1 = new ConcreteFactory1();
ClientCode(*factory1);
ConcreteFactory2* factory2 = new ConcreteFactory2();
ClientCode(*factory2);
delete factory1;
delete factory2;
Just like we saw, if the exact number of product families are unknown and you have to add new ones in the future, we can use this pattern.
4. Builder
What is the builder design pattern?
In easy words, I hate my constructor looks ugly and long.. like
new House( ... x 100);
and we can customize the Builder class to take in its own parameters and options. And plus, We can optionally implement a manager class called Director that can be responsible for different subroutines that call different methods in a builder class.
Lets check a UML
And base on the UML, lets dive in to the code
Builder interface
class Builder{
public:
virtual ~Builder(){}
virtual void AddTopping(string topping) const =0;
virtual void AddBase(string base) const =0;
virtual void AddSauce(string sauce) const =0;
};
ConcreteBuilder1
class ConcreteBuilder1 : public Builder{
private:
Product1* product;
public:
ConcreteBuilder1(){
this->Reset(); //builder has an instance of Product ready
}
~ConcreteBuilder1(){
delete product;
}
void Reset(){
this->product= new Product1();
}
/**
* All production steps work with the same product instance.
*/
void AddTopping(string topping)const override{
this->product->toppings.push_back(topping);
}
void AddBase(string base)const override{
this->product->base = base;
}
void AddSauce(string sauce)const override{
this->product->sauce = sauce;
}
/**
* Please be careful here with the memory ownership. Once you call
* GetProduct the user of this function is responsable to release this
* memory. Here could be a better option to use smart pointers to avoid
* memory leaks
*/
Product1* GetProduct() {
Product1* result= this->product;
this->Reset(); //resets product pointer to be ready for next pizza to build
return result;
}
};
Product1
class Product1{
public:
std::vector<std::string> toppings;
string base = "";
string sauce = "";
friend ostream& operator<<(ostream& os, const Product1& p) {
stringstream toppings_str;
os << "Product parts: ";
for (size_t i=0;i<p.toppings.size();i++){
if(p.toppings[i]== p.toppings.back()){
toppings_str << p.toppings[i];
}else{
toppings_str << p.toppings[i] << ", ";
}
}
os << p.base << ", ";
os << p.sauce << ", ";
os << toppings_str.str();
os << "\n\n";
return os;
}
};
Director (Completely Optional)
class Director{
/**
* @var Builder
*/
private:
Builder* builder;
/**
* The Director works with any builder instance that the client code passes
* to it. This way, the client code may alter the final type of the newly
* assembled product.
*/
public:
void set_builder(Builder* builder){
this->builder=builder;
}
/**
* The Director can construct several product variations using the same
* building steps.
*/
void BuildMinimalViableProduct(){
this->builder->AddBase("Wheat Pizza");
}
void BuildFullFeaturedProduct(){
this->builder->AddBase("Wheat Pizza");
this->builder->AddSauce("Tomato");
this->builder->AddTopping("Pepperoni");
this->builder->AddTopping("Mozzarella");
this->builder->AddTopping("Olives");
}
};
Client Code
void ClientCode(Director& director)
{
ConcreteBuilder1* builder = new ConcreteBuilder1();
director.set_builder(builder);
//Use director to call builder to create pre-made products for us
std::cout << "Standard basic product:\n";
director.BuildMinimalViableProduct();
Product1* p= builder->GetProduct();
cout << *p;
delete p;
std::cout << "Standard full featured product:\n";
director.BuildFullFeaturedProduct();
p = builder->GetProduct();
cout << *p;
delete p;
// Remember, the Builder pattern can be used without a Director class.
std::cout << "Custom product:\n";
builder->AddBase("Sour dough Pizza");
builder->AddTopping("Mushrooms");
builder->AddSauce("Sour cream");
p=builder->GetProduct();
cout << *p;
delete p;
delete builder;
}
At last in main
Director* director= new Director();
ClientCode(*director);
delete director;
return 0;
As you can see we can get rid of numerous constructors. And When a product is built out of many parts that can be mixed and matched.
This allows you to build variations while avoiding inheritance hierarchies
Top comments (0)