7.4. Abstract Factory

  • EN: Abstract Factory

  • PL: Fabryka Abstrakcyjna

  • Type: object

The Abstract Factory design pattern is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes.

Here's a simple example of the Abstract Factory pattern in Python:

>>> class AbstractFactory:
...     def create_product_a(self):
...         pass
...
...     def create_product_b(self):
...         pass
...
>>> class ConcreteFactory1(AbstractFactory):
...     def create_product_a(self):
...         return ConcreteProductA1()
...
...     def create_product_b(self):
...         return ConcreteProductB1()
...
>>> class ConcreteFactory2(AbstractFactory):
...     def create_product_a(self):
...         return ConcreteProductA2()
...
...     def create_product_b(self):
...         return ConcreteProductB2()
...
>>> class AbstractProductA:
...     pass
...
>>> class ConcreteProductA1(AbstractProductA):
...     pass
...
>>> class ConcreteProductA2(AbstractProductA):
...     pass
...
>>> class AbstractProductB:
...     pass
...
>>> class ConcreteProductB1(AbstractProductB):
...     pass
...
>>> class ConcreteProductB2(AbstractProductB):
...     pass
...
>>> factory1 = ConcreteFactory1()
>>> product_a1 = factory1.create_product_a()
>>> product_b1 = factory1.create_product_b()
>>> factory2 = ConcreteFactory2()
>>> product_a2 = factory2.create_product_a()
>>> product_b2 = factory2.create_product_b()

In this example, AbstractFactory is an interface for creating objects in a super factory which creates other factories. This factory is also called as factory of factories. This type of design pattern comes under creational pattern as this pattern provides one of the best ways to create an object. ConcreteFactory1 and ConcreteFactory2 are concrete classes that implement the AbstractFactory interface and define the create_product_a and create_product_b methods.

7.4.1. Pattern

  • Provide an interface for creating families of related objects

  • Factory Method is a method

  • Abstract Factory is an abstraction (interface)

  • Used for theme support (which generates buttons, inputs etc)

../../_images/designpatterns-abstractfactory-pattern.png

7.4.2. Problem

  • Violates Open/Close Principle

  • Hard to add a new theme

  • Easy to accidentally use Material widget inside of Flat theme block

../../_images/designpatterns-abstractfactory-problem.png
from dataclasses import dataclass
import sys


# iOS Components

@dataclass
class iOSLabel:
    name: str
    def render(self):
        return 'iOS Label'

@dataclass
class iOSButton:
    name: str
    def render(self):
        return 'iOS Button'


# Android Components

@dataclass
class AndroidLabel:
    name: str
    def render(self):
        return 'Android Label'

@dataclass
class AndroidButton:
    name: str
    def render(self):
        return 'Android Button'


# Main

if __name__ == '__main__':

    if sys.platform == 'ios':
        iOSLabel('username')
        iOSLabel('password')
        iOSButton('login')
    elif sys.platform == 'android':
        AndroidLabel('username')
        AndroidLabel('password')
        AndroidButton('login')

7.4.3. Solution

from dataclasses import dataclass
import sys


# iOS Components (the same as before)

@dataclass
class iOSLabel:
    name: str
    def render(self):
        return 'iOS Label'

@dataclass
class iOSButton:
    name: str
    def render(self):
        return 'iOS Button'


# Android Components (the same as before)

@dataclass
class AndroidLabel:
    name: str
    def render(self):
        return 'Android Label'

@dataclass
class AndroidButton:
    name: str
    def render(self):
        return 'Android Button'


# Abstract Factories

class iOSFactory:
    def create_label(self, name):
        return iOSLabel(name)

    def create_button(self, name):
        return iOSButton(name)


class AndroidFactory:
    def create_label(self, name):
        return AndroidLabel(name)

    def create_button(self, name):
        return AndroidButton(name)


# Main

if __name__ == '__main__':

    if sys.platform == 'ios':
        factory = iOSFactory()
    elif sys.platform == 'android':
        factory = AndroidFactory()

    factory.create_label('username')
    factory.create_label('password')
    factory.create_button('login')

7.4.4. Assignments

# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author

# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`

# %% About
# - Name: DesignPatterns Creational AbstractFactory
# - Difficulty: easy
# - Lines: 70
# - Minutes: 21

# %% English
# 1. Implement Abstract Factory pattern
# 2. Run doctests - all must succeed

# %% Polish
# 1. Zaimplementuj wzorzec Abstract Factory
# 2. Uruchom doctesty - wszystkie muszą się powieść

# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'

>>> from pprint import pprint

>>> main(Platform.iOS)
iOS Textbox username
iOS Textbox password
iOS Button submit

>>> main(Platform.Android)
Android Textbox username
Android Textbox password
Android Button submit
"""
from dataclasses import dataclass
from enum import Enum


class Platform(Enum):
    iOS = 'iOS'
    Android = 'Android'


@dataclass
class Button:
    name: str

    def render(self, platform: Platform):
        if platform is platform.iOS:
            print(f'iOS Button {self.name}')
        elif platform is platform.Android:
            print(f'Android Button {self.name}')

@dataclass
class Textbox:
    name: str

    def render(self, platform: Platform):
        if platform is platform.iOS:
            print(f'iOS Textbox {self.name}')
        elif platform is platform.Android:
            print(f'Android Textbox {self.name}')


def main(platform: Platform):
    Textbox('username').render(platform)
    Textbox('password').render(platform)
    Button('submit').render(platform)