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
Animal
s have aneat()
function, commonly implemented between all animals, but each have uniquespeak()
functions so the abstractAnimal
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 aMessengerFactory
creates messengers. Alternatively, you can focus on some common features, and say - create aBasicFactory
andSecureFactory
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")
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!
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:
$ 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.