Factory Pattern aka Factory Method, Simple Factory is one of the most common Design Patterns out there. the Factory Pattern separates the creation of objects from its use case, in this pattern you have a factory object which holds the logic behind the object instantiation and returns it at the runtime.
Factory Pattern also has another variation, usually known as Abstract Factory Pattern aka Factory Pattern and the difference is that Abstract Factory adds a layer of abstraction where factories are used by another main factory.
Factory Pattern ๐ญ
Let's create a localization feature by having an abstract class called Localizer
that will be extended later by the different languages we want to support.
This module provides the infrastructure for defining abstract base classes (ABCs) in Python
from abc import ABC, abstractmethod
class Localizer(ABC):
@abstractmethod
def localize(self, msg):
""" Translate message """
pass
We will add 3 different languages: English, German and Arabic.
Each of them extends the Localizer
class and config a dict of translations in the constructor as well as overrides the localize
function
starting with the German
class GermanLocalizer(Localizer):
"German version"
def __init__(self):
""" Setup translations """
self.translations = {"main.header": "Ich spreache deutsch"}
def localize(self, msg):
"""Returns msg in German"""
return self.translations.get(msg, msg)
then we add the others, and the code becomes
from abc import ABC, abstractmethod
class Localizer(ABC):
@abstractmethod
def localize(self, msg):
""" Translate message """
pass
class GermanLocalizer(Localizer):
"German version"
def __init__(self):
""" Setup translations """
self.translations = {"main.header": "Ich spreache deutsch"}
def localize(self, msg):
"""Returns msg in German"""
return self.translations.get(msg, msg)
class ArabicLocalizer(Localizer):
"""Arabic version"""
def __init__(self):
""" Setup translations """
self.translations = {"main.header": "ุงุชููู
ุงูุนุฑุจูุฉ"}
def localize(self, msg):
"""Returns msg in Arabic"""
return self.translations.get(msg, msg)
class EnglishLocalizer(Localizer):
"""English version"""
def __init__(self):
""" Setup translations """
self.translations = {"main.header": "I speak English"}
def localize(self, msg):
"""Returns msg in English"""
return self.translations.get(msg, msg)
The Problem ๐ฉ
Now if we DON'T want to use the Factory pattern our main function will be something like
def main():
print("Please select your language:")
print("1. English")
print("2. German")
print("3. Arabic")
option = input("Please select an option then hit enter:")
local = EnglishLocalizer()
if option == '1':
local = EnglishLocalizer()
if option == '2':
local = GermanLocalizer()
if option == '3':
local = ArabicLocalizer()
msg = "main.header"
print(local.localize(msg))
Each time we want to add some message on the shell, we will have to add those ugly if statements! Imagine how bad would it be if we decided to add a new language?!
The Solution ๐
We add the Factory method!
class Translate:
@staticmethod
def get_localizer(option="1") -> Localizer:
"""Factory Method"""
localizers = {
"1": EnglishLocalizer,
"2": GermanLocalizer,
"3": ArabicLocalizer,
}
return localizers[option]()
So main
becomes
def main():
print("Please select your language:")
print("1. English")
print("2. German")
print("3. Arabic")
option = input("Please select an option then hit enter:")
local = Translate.get_localizer(option)
msg = "main.header"
print(local.localize(msg))
Output
By having that Factory method, all the logic that handles localization is now in one function, whenever we want to change anything regarding the logic, there is only one function to modify get_localizer
.
Abstract Factory Pattern ๐ญ
You are developing a cross-platform application, where buttons, sliders, and dialogs will be unique on each OS.
each UI component has two variations, either Windows or MacOS
class IButton(ABC):
@abstractmethod
def onClick(func: function):
pass
class ISlider(ABC):
@abstractmethod
def onSlide(func: function):
pass
class WinButton(IButton):
"""Button of Windows design system"""
def onClick(func):
func()
class MacButton(IButton):
"""Button of macOS design system"""
def onClick(func):
func()
class WinSlider(ISlider):
"""Slider of Windows design system"""
def onSlide(func):
func()
class MacSlider(ISlider):
"""Slider of macOS design system"""
def onSlide(func):
func()
The Problem ๐ฉ
On each page of your application, you have to do something like
def main():
is_windows = os.name == 'nt'
button = WinButton() if is_windows else MacButton()
slider = WinSlider() if is_windows else MacSlider()
The Solution ๐
To solve this, we will create a factory interface, and both MacUiFactory
and WindowsUiFactory
will extend it
class IGUI(ABC):
@abstractmethod
def create_button() -> IButton:
pass
@abstractmethod
def create_slider() -> ISlider:
pass
class WinUiFactory(IGUI):
def create_button():
return WinButton()
def create_slider():
return WinSlider()
class MacUiFactory(IGUI):
def create_button():
return MacButton()
def create_slider():
return MacSlider()
We will add another class UI
which will compose IGUI
factory
class UI:
"""Config the current factory"""
def __init__(self) -> None:
is_windows = os.name == 'nt'
self.factory = WinUiFactory() if is_windows else MacUiFactory
And now, we can easily use the correct UI elements without repeating our code, main
becomes:
def main():
ui = UI()
button = ui.factory.create_button()
slider = ui.factory.create_slider()
print(button) # <__main__.MacButton object at 0x7f11599515b0>
print(slider) # <__main__.MacSlider object at 0x7f11599515e0>
You won't now have to change the whole code base if you decided to support a new OS.
When to use it? ๐ค
Factory Pattern: Use it whenever you have a complex logic behind the creation of a single object.
Abstract Factory Pattern: Use it when you have different variations of objects that are unknown at the definition time.
Pros and Cons ๐
Pros โ
- it supports the Single Responsibility Principle (SOLID); by separating the logic of creation out of the use case
- it supports Open/Closed Principle (SOLID); by separating the logic of creation out of the use case which will allow adding more variants or objects without breaking the client's code.
Cons โ
- The code might get complex because it introduces many more interfaces and abstract classes
Code โจ
You can review the whole code that was used on this GitHub repo
Conclusion
If your code is repetitive with the same if statements, you probably need something like the Factory Pattern, but always keep in mind that overusing the Factory Pattern will result in a more complex, hard-to-read code.
You might notice that in this pattern the subclass decides what class to be instantiated and that would be decided in the runtime.
I'm planning to cover the most common design patterns. if you are interested, you can follow me to get notified of any new articles that I publish in the future or you can check the below list of my published articles about design patterns: