Abstract Factory Design Pattern in Python

Abstract Factory Design Pattern in Python

Introduction

Design patterns help make code understandable, scalable, and reusable. There are different categories of design patterns, depending on the problems being addressed. When code is modularized, it's easier to add newer features without making drastic changes to the project.

The Abstract Factory Design Pattern is a creational pattern that provides a framework to create interfaces of closely related objects without specifying their classes - hence the term "abstract".

An abstract class is partially implemented and defines the requirements that its child classes should have and some generic child behavior as well as what functions they should have. Concrete classes extend abstract classes and provide the unimplemented functionality, while inheriting the common functionalities.

Abstract Classes in Python

For instance, all Animals have an eat() function, commonly implemented between all animals, but each have unique speak() functions so the abstract Animal class leaves that up to the children.

In Python, every abstract class is derived from the ABC class of the abc module. The abstract method is declared inside the abstract class but not implemented, and all implemented methods are passed down to concrete classes.

In the following example, notice that the AbstractClass definition contains an @abstractmethod decorator. By decorating a function with it, we define that all child classes must have an implementation of the func() method, as there is no default, common implementation:

from abc import ABC, abstractmethod

class AbstractClass(ABC):
    @abstractmethod
    def func():
        pass

The partial implementation of the parent abstract factory class is fulfilled by its children/concrete class. Each child class will contain a func() method to fulfill the abstract class' requirements.

Building from our previous example, we can create a child class like this:

from abc import ABC, abstractmethod

class AbstractClass(ABC):
    @abstractmethod
    def func(self):
        pass

class ChildClass(AbstractClass):
    def func(self):
        out = "This is an output"
        return out

obj = ChildClass()
print(obj.func())

Abstract Factory Design Pattern in Python

Coming back to the definition of the Abstract Factory Pattern:

Through the Abstract Factory Pattern, we define interfaces to create families of related objects without specifying their concrete classes.

Therefore, the Abstract Factory Pattern delegates the creation of objects to another class. This pattern is best suited when one wishes to create multiple categories of an object by abstracting its implementation.

You may be familiar with the concept factories - these are objects that create other objects. The Abstract Factory Pattern is primarily concerned with the interface for factory objects.

What's a family of objects?

A table can be round, square or oval. In a similar sense, a common class such as an Employee might branch out to several more concrete classes.

Here's a visual representation of the Abstract Factory Pattern:

Assume that you are designing a family of two products (a browser and a messenger).

  • Abstract Products: Two abstract classes are created, one for the browser and another for the messenger. These classes contain abstract methods that are mandatory for the construction of the products. These abstract classes are referred to as interfaces.

    In the example shown above, the Web Browser and Messenger are the abstract products.

  • Concrete Products: Concrete products inherit the abstract methods from the abstract classes i.e. abstract products. Using the interfaces, different families of products can be created.

    For example, in the diagram above, three different kinds of web browsers are created for three different sets of users. If there's one thing that all these concrete products have in common, that would be the abstract methods that were defined in the abstract class.

  • Concrete Factories: Concrete Factories create Concrete Products as directed by the Abstract Factories. The concrete factories are only capable of creating those products that are specified in them - a BrowserFactory creates browsers, while a MessengerFactory creates messengers. Alterantively, you can focus on some common features, and say - create a BasicFactory and SecureFactory which create basic or secure web browsers and messenger instances.

    In the diagram mentioned above, the Vanilla Products Factory is capable of creating both vanilla concrete products (browser and messenger), while the Secure Products Factory makes safe versions.

  • Abstract Factories: The Abstract factories possess interfaces to create the abstract products i.e. they contain several methods that return abstract products.

    In the example, the interfaces of concrete factories are invoked to get the abstract products as a web browser and messenger.

Implementation

Acquainted with the terminology, let's try implementing the Abstract Factory Pattern in Python.

First off, we create the abstract products - Browser and Messenger as:

from abc import ABC, abstractmethod

class Browser(ABC):
    """
    Creates "Abstract Product A"
    """

    # Interface - Create Search Toolbar
    @abstractmethod
    def create_search_toolbar(self):
        pass

    # Interface - Create Browser Window
    @abstractmethod
    def create_browser_window(self):
        pass

class Messenger(ABC):
    """
    Creates "Abstract Product B"
    """

    @abstractmethod
    # Interface - Create Messenger Window
    def create_messenger_window(self):
        pass

These act as base classes to the following concrete classes i.e. product variants of the abstract products - vanilla and secure:

class VanillaBrowser(Browser):
    """
    Type: Concrete Product
    Abstract methods of the Browser base class are implemented.
    """

    # Interface - Create Search Toolbar
    def create_search_toolbar(self):
        print("Search Toolbar Created")

    # Interface - Create Browser Window]
    def create_browser_window(self):
        print("Browser Window Created")


class VanillaMessenger(Messenger):
    """
    Type: Concrete Product
    Abstract methods of the Messenger base class are implemented.
    """

    # Interface - Create Messenger Window
    def create_messenger_window(self):
        print("Messenger Window Created")

class SecureBrowser(Browser):
    """
    Type: Concrete Product
    Abstract methods of the Browser base class are implemented.
    """

    # Abstract Method of the Browser base class
    def create_search_toolbar(self):
        print("Secure Browser - Search Toolbar Created")

    # Abstract Method of the Browser base class
    def create_browser_window(self):
        print("Secure Browser - Browser Window Created")

    def create_incognito_mode(self):
        print("Secure Browser - Incognito Mode Created")


class SecureMessenger(Messenger):
    """
    Type: Concrete Product
    Abstract methods of the Messenger base class are implemented.
    """

    # Abstract Method of the Messenger base class
    def create_messenger_window(self):
        print("Secure Messenger - Messenger Window Created")

    def create_privacy_filter(self):
        print("Secure Messenger - Privacy Filter Created")

    def disappearing_messages(self):
        print("Secure Messenger - Disappearing Messages Feature Enabled")

You can notice that apart from the abstract methods, there are also extra features added to the concrete products to make them functional in their own context.

We are almost there. Let's now create the abstract factory itself and the corresponding concrete factories as:

class AbstractFactory(ABC):
    """
    The Abstract Factory
    """

    @abstractmethod
    def create_browser(self):
        pass

    @abstractmethod
    def create_messenger(self):
        pass

class VanillaProductsFactory(AbstractFactory):
    """
    Type: Concrete Factory
    Implement the operations to create concrete product objects.
    """

    def create_browser(self):
        return VanillaBrowser()

    def create_messenger(self):
        return VanillaMessenger()

class SecureProductsFactory(AbstractFactory):
    """
    Type: Concrete Factory
    Implement the operations to create concrete product objects.
    """

    def create_browser(self):
        return SecureBrowser()

    def create_messenger(self):
        return SecureMessenger()

In the code above, since the AbstractFactory acts as a base class, the abstract methods are instantiated just as mentioned in the base class.

Let's add a main() method so we an see our example in action:

def main():
    for factory in (VanillaProductsFactory(), SecureProductsFactory()):
        product_a = factory.create_browser()
        product_b = factory.create_messenger()
        product_a.create_browser_window()
        product_a.create_search_toolbar()
        product_b.create_messenger_window()

if __name__ == "__main__":
    main()

If you'd like to see the entire code file, you can find it in the repo on GitHub.

If we execute our code, it returns the following output showing the creation of the secure browser and the secure messenger:

Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

$ python3 abstract_factory_code.py
Browser Window Created
Search Toolbar Created
Messenger Window Created
Secure Browser - Browser Window Created
Secure Browser - Search Toolbar Created
Secure Messenger - Messenger Window Created

Pros & Cons

Now that we have implemented the pattern, let's weigh their pros and cons.

Pros:

  • The main advantage of this pattern is flexibility - the ability to add newer features and functions to the existing products or perhaps even add newer concrete products to the concrete factories. This can be done without sabotaging the whole code.

  • There is minimal direct interaction between the client and the concrete products. There is also flexibility in organizing and compacting the code.

Cons

  • The major drawback of this pattern is the readability and maintainability of the code. Although it provides a flexible way to add new futures, adding a new component will require adding to the concrete classes, modifying the interfaces, etc. The cascading effects of modification take development time.

Conclusion

The Abstract Factory Pattern can be used very effectively for closely related families of different products unlike the Factory Pattern, which can be used only for a single type of product.

If you are interested in learning more about it, read our guide to the Factory Pattern in Python.

The Abstract Factory Pattern solves a major crisis for the need to write clean code. We have covered the basics of this pattern and also understood the implementation using an example.

Last Updated: September 8th, 2021
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

Sathiya Sarathi GunasekaranAuthor

Pythonist 🐍| Linux Geek who codes on WSL | Data & Cloud Fanatic | Blogging Advocate |
Author

Want a remote job?

    Prepping for an interview?

    • Improve your skills by solving one coding problem every day
    • Get the solutions the next morning via email
    • Practice on actual problems asked by top companies, like:
     
     
     

    Make Clarity from Data - Quickly Learn Data Visualization with Python

    Learn the landscape of Data Visualization tools in Python - work with Seaborn, Plotly, and Bokeh, and excel in Matplotlib!

    From simple plot types to ridge plots, surface plots and spectrograms - understand your data and learn to draw conclusions from it.

    © 2013-2021 Stack Abuse. All rights reserved.