Abstract Factory Pattern Explained!

Abstract Factory design pattern is a creational design pattern which is very similar to the Factory design pattern. So it’s very critical we understand them properly to determine exactly which pattern fits specific situations. Like most of our design patterns, this too is super useful in interviews as well as our day-to-day coding work.

 

Factory Design Pattern recap:

Before we dive into the Abstract Factory design pattern, let’s do a recap of the Factory design pattern first. You might remember our example of the Vehicle Library system! In a vehicle library system, we have an if-else block set up to create the right kind of vehicle that our client desires, and we moved that logic out of the client code and into the factory.

Why did we do this?

Because we want the system to be able to incorporate brand new vehicle type creation without making any changes to the client side, with the factory taking care of all the creation logic. The client is decoupled from the factory and doesn’t care about any of the implementation. We won’t have to edit the client code or compile it every time, we only need to modify our factory.

Problem Statement:

Extending our Vehicle Library example, what if our client now wants specific types of a car itself – like say a Hyundai and a Toyota? Attempting to put all these possibilities for all our vehicles into a single if-else block is going to give us a very long, heavily nested if-else block that is no longer efficient. Of course, once again the client should not have to worry about any of these complexities. This is a perfect scenario to use the Abstract Factory design pattern.

If you had to describe the difference between these 2 patterns in a single statement, here’s what you could say!

In the Factory design pattern, we create concrete classes or objects, but in the Abstract Factory design pattern, we create factories that will make objects.

Essentially, our Abstract Factory pattern will result in the creation of a family of classes.

Going back to our example, in the Abstract Factory pattern, we’ll create different kinds of factories like the Toyota factory or a Hyundai factory that could create cars or bikes within those factories.

In the workflow of the pattern, we then have the Client who will use a Vehicle Factory to create a specific vehicle (say a Hyundai car). Depending on the input, the corresponding factory (Hyundai factory) will create the required vehicle (Hyundai car). This is an additional level of implementation to the Factory design pattern where there’s a layer of factories beneath the Vehicle Factory.

Let’s now look at an example closer to home.

Assume you’re a front-end developer who has to create a button or a text box. You could be working on any operating system, right? You might be working on Windows, or MacOS, or something else. But when specifying our project, we don’t go ahead and state that we’re building a button/text box on Windows/Mac. Depending on the operating system, we should get a button or a text box.

How does this work? Let’s understand this better.

So our client needs to get a hold of a button/text box. We initially build a GUIFactory which will be informed what operating system we’re working on. Depending on this input, a MacFactory or a WindowsFactory is going to be created. And these factories give you the corresponding button/text box.

All you need to ensure is that you inform the GUIFactory upon its creation whether you’re working on Mac or Windows. And then you just request for a button or a text box and the creation is handled by the factory. If in the future you want to support more GUI elements or operating systems, the changes are all happening in the factory and not on the client side.

 

Implementation:

  • Let’s start with the client code. The first thing we want to confirm is what operating system (osType) we are using, and so we’ll prompt this from the user.
  • In order to get the button/text box, we’re going to need a factory. Since we don’t want to keep making changes to what the client engages with, we’re going to create an Interface Factory from which the MacFactory and the WinFactory are going to be inherited.
  • For the sake of our main(), let’s assume the interface IFactory is already set up. We’ll use the static CreateFactory() function within our GUIAbstractFactory class to provide the client with a factory.
  • Now we have our factory! We can go ahead and create our button/text box. Once again, we don’t specify what kind of button it is – Mac or Windows. We’ll thus use an interface again, IButton class, and use our factory’s CreateButton() function. Similarly for the text box, we’ll have ITextBox and CreateTextBox().

This is the client code.

int main() {
    cout<<"Enter your machine OS "<<endl;
    string osType;
    cin>>osType;

    IFactory* fact = GUIAbstractFactory::CreateFactory(osType);

    IButton* button = fact->CreateButton();
    button->press();

    ITextBox* textBox = fact->CreateTextBox();
    textBox->showText();

    return 0;
}
  • Let’s now create the classes we’ve talked about.
  • Starting with the button interface, IButton, we create a pure virtual function press() that will have to be implemented by all classes inherited from this interface. Let’s inherit two classes from this IButton: WinButton and MacButton. And we’ll implement the press() function in the public section of both these classes.
class IButton {
    public:
        virtual void press() = 0;
};

class MacButton : public IButton {
    public:
        void press() {
            cout<<" Mac Button Pressed"<<endl;
        }
};

class WinButton : public IButton {
    public:
        void press() {
            cout<<" Win Button Pressed"<<endl;
        }
};
  • Similarly, we construct a textbox interface as well, ITextBox, along with the inheriting classes WinTextBox and MacTextBox. Instead of press(), we have the relevant showText() function for our textbox classes, which we’ll use to show the corresponding messages for separate operating systems just to show that different factories are being used in each case.
class ITextBox {
    public:
        virtual void showText() = 0;
};

class MacTextBox : public ITextBox {
    public:
        void showText() {
            cout<<" Showing Mac TextBox"<<endl;
        }
};

class WinTextBox : public ITextBox {
    public:
        void showText() {
            cout<<" Showing Win TextBox"<<endl;
        }
};
  • Now that we have all the button/textbox classes required, we need to develop a Factory interface that we’ll call IFactory, from which the MacFactory and WinFactory will be inherited. Let’s set up all the functions the inheriting classes must implement – CreateButton() and CreateTextBox() – in the public section of our interface. These functions return the IButton and ITextBox types respectively because once again, we want this to be extendible to any kind of operating system.
  • In our MacFactory and WinFactory, we implement these two functions which will return the button/textbox depending on which factory is building it. Thus, the MacFactory will return the MacButton and MacTextBox while the WinFactory returns the WinButton and WinTextBox for these 2 functions.
class IFactory {
    public:
        virtual IButton* CreateButton() = 0;
        virtual ITextBox* CreateTextBox() = 0;
};

class MacFactory : public IFactory {
    public:
        IButton* CreateButton() {
            return new MacButton();
        }
        ITextBox* CreateTextBox() {
            return new MacTextBox();
        }
};

class WinFactory : public IFactory {
    public:
        IButton* CreateButton() {
            return new WinButton();
        }
        ITextBox* CreateTextBox() {
            return new WinTextBox();
        }
};

 

  • The only thing left to do is to create a GUIAbstractFactory which is actually responsible for creating the right factory for us. As we mentioned earlier, we’ve gotten away with just using the static function of the class rather than creating a GUIAbstractFactory object to invoke the CreateFactory() function.
  • And here in this class, is where we place the if-else logic for choosing the right factory (MacFactory/WinFactory) depending on the osType.

Note that, ideally, there must be a DefaultFactory which we can return in case the wrong input is provided but let’s keep things simple for our example.

class GUIAbstractFactory {
    public:
        static IFactory* CreateFactory(string osType) {
            if(osType == "windows")
                return new WinFactory();
            else if(osType == "mac")
                return new MacFactory();
            return new MacFactory();
        }
};

And in our output, we can now see that depending on the operating system type provided, we see that the corresponding button and textbox have been created (indicated through the custom print messages we set up in our MacFactory and WinFactory).

int main() {
    cout<<"Enter your machine OS "<<endl;
    string osType;
    cin>>osType;

    IFactory* fact = GUIAbstractFactory::CreateFactory(osType);

    IButton* button = fact->CreateButton();
    button->press();

    ITextBox* textBox = fact->CreateTextBox();
    textBox->showText();

    return 0;
}

Conclusion:

So what exactly did we do here?

We had a GUIAbstractFactory to which we specified our OS type, and that we needed a factory which it created depending on our OS. Once we got the factory, we asked it to make the button/textbox for us. Thus, 2 layers of factories were involved in this design pattern, and that summarizes the whole concept of the Abstract Factory design pattern!

We hope this was clear and you found it easy to follow along! If you’ve seen the LLD Roadmap, you’ll know that the Factory and Abstract Factory design patterns are pretty crucial and worth having in your toolkit.

Would you like to watch a video walking you through the Abstract Factory design pattern implementation? Check out our video on the official YouTube channel!

Was This Helpful?

Scan to Pay With Your Favourite Upi App
to Buy me a Coffee!

keerti upi qr code