Avash's Portfolio

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

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.

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.

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

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

❌ Cons