Mastering Factory Design Patterns through the Construction of a Banking System
Mastering Factory Design Patterns through the Construction of a Banking System
First of all, let’s define the
Requirements of our problem
There can be multiple banks
All banks share some common attributes and behavior like
Behavior -
showBalance()
,withdraw()
,addBalance()
etcAttributes -
amount_left
,num_of_users
etc.
Banks can also implement their unique methods and attributes
Provide a simple interface to the user for creating
bank
objects of their choice (i.e., users should now be responsible for)Our solution should follow S.O.L.I.D. Principle
Having Multiple Banks
Let’s start by defining two banks classes - A1
and A2
class A1 {
private:
int amount_left , num_of_users; // .. other params
public:
int NumOfUsers(){...}
// Other attributes
};
class A2 {
private:
int amount_left , num_of_users; // .. other params
public:
int NumOfUsers(){...}
// other attributes
};
Notice something wrong.
First of all, there is a ton of code duplication. Since there are a lot of common attributes and methods we will be writing, the same code again and again.
Secondly, there is no guarantee that all classes will define the common attributes and methods.
To solve these issues, we can define a common interface. This common interface will have all common attributes and methods. All other classes that implement this common interface will have to define the methods.
It solves the code duplication part, as we don’t need to redefine the attributes and methods in the subclasses. (classes that implement the interface)
Any class that implements the common interface needs to define the methods.
Let’s have a look at the code now.
class Bank{
protected:
int amount_left , num_of_users; // .. other common attributes
public:
virtual int NumOfUsers(){ // Subclass can redefine this
return this->num_of_users
}
virtual int getBalance() const = 0; //Subclass have to redefine this
};
class A1 : public Bank {
private:
int special_params;
public:
int getBalance() const override{...}
};
class A2 : public Bank {
private:
int special_params;
public:
int getBalance() const override{...}
};
Okay, now that this is taken care of, let’s focus on the second requirement.
Provide a simple interface to the user for creating a bank
objects of their choice
In our existing code, if a user wants to create a bank object then
void client(){
A1* a1 = new A1;
std::cout<<a1->getBalance();
}
Well, this doesn’t look that bad, but we are exposing too much of our backend logic to the client. Ideally, the client shouldn’t know about every subclass. We should be providing a centralized code for bank selection. Let’s define a new class for this.
class BankCreator{
Bank* getBankInstance(string bankName){
switch(bankName){
case 'a1':
return new A1;
case 'a2':
return new A2;
default:
return NULL;
}
}
};
void client(){
BankCreator* backCreator = new BankCreator;
Bank* a1 = backCreator->getBankInstance('a1');
Bank* a2 = backCreator->getBankInstance('a2');
}
NOTE: All the objects created by the
BankCreator
class should have a common superclass.
Now, this looks great. But do you notice something wrong with our code?
The BankCreator
class violates the Open For Extension and Closed for Modification Principle.
When we are adding a new bank (subclass) we need to modify the BankCreator
class (add another case to the switch statement).
So, we need to define the creator class in such a way that whenever we add/remove a bank subclass, we do so without modifying the creator class. To do this
We first make the creator a class an interface
class BankCreator{ protected: virtual Bank* getBankInstance() const = 0; };
Now to add a new
bank
we simply implement this interfaceclass A1Creator: public BankCreator{ Bank* getBankInstance() const override{ return new A1; } }; class A2Creator: public BankCreator{ Bank* getBankInstance() const override{ return new A2; } };
Since we can add new bank subclasses without modifying the creator class, we can say that we are following the Open For Extension and Closed for Modification Principle.
Now a client can create a bank object in the following way
void client(){
A1Creator* a1creator = new A1Creator;
A1* a1 = a1creator->getBankInstance();
A1* a11 = a1creator->getBankInstance();
}
Also, it might look like we are exposing too much of our backend logic, but that’s not the case. The client is not concerned about the implementation of the concrete subclasses (
bank subclasses
) but is only aware of creator classes.
Before moving forward, here’s the entire
Pseudo code for Factory Design Pattern
//This is an Abstract class. But you can also make it an interface
class Bank{
protected:
int amount_left , num_of_users; // .. other common attributes
public:
virtual int NumOfUsers(){ // Subclass can redefine this
return this->num_of_users
}
virtual int getBalance() const = 0; //Subclass have to redefine this
};
class A1 : public Bank {
private:
int special_params;
public:
int getBalance() const override{...}
};
class A2 : public Bank {
private:
int special_params;
public:
int getBalance() const override{...}
};
class BankCreator{
protected:
virtual Bank* getBankInstance() const = 0;
};
class A1Creator: public BankCreator{
Bank* getBankInstance() const override{
return new A1;
}
};
class A2Creator: public BankCreator{
Bank* getBankInstance() const override{
return new A2;
}
};
void client(){
A1Creator* a1creator = new A1Creator;
A1* a1 = a1creator->getBankInstance();
A1* a11 = a1creator->getBankInstance();
}
Now, let’s discuss the pros and cons of this pattern
✅ Pros
It follows the Single Responsibility Principle.
The
creator
class and eachconcrete creator
subclass have only one responsibility. Tldr, there is a segregation of responsibility.It follows the Open For Extension and Closed for Modification Principle.
There is loose coupling
Instead of adding the logic to decide the type of object in the
creator
class, we are assigning the responsibility to some other class
❌ Cons
Code can become too complex.
Since we are adding a lot of subclasses.