8.4. Abstract Factory¶
EN: Abstract Factory
PL: Fabryka Abstrakcyjna
Type: object
The Abstract Factory pattern differs from the Factory Pattern in that it returns Factories, rather than objects of concrete class.
8.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)

8.4.2. Problem¶
Violates Open/Close Principle
Hard to add a new theme
Easy to accidentally use Material widget inside of Flat theme block

from abc import ABC, abstractmethod
from enum import Enum
#%% Interfaces
class Widget(ABC):
@abstractmethod
def render(self) -> None:
raise NotImplementedError
class Button(Widget):
pass
class Textbox(Widget):
pass
#%% Material Theme
class MaterialButton(Button):
def render(self) -> None:
print('Material Button')
class MaterialTextbox(Textbox):
def render(self) -> None:
print('Material Textbox')
#%% Flat Theme
class FlatButton(Button):
def render(self) -> None:
print('Flat Button')
class FlatTextbox(Textbox):
def render(self) -> None:
print('Flat Textbox')
#%% Main
class Theme(Enum):
MATERIAL = 1
FLAT = 2
class ContactForm:
def render(self, theme: Theme) -> None:
match theme:
case Theme.MATERIAL:
MaterialTextbox().render()
MaterialButton().render()
case Theme.FLAT:
FlatTextbox().render()
FlatButton().render()
if __name__ == '__main__':
ContactForm().render(Theme.FLAT)
# Flat Textbox
# Flat Button
ContactForm().render(Theme.MATERIAL)
# Material Textbox
# Material Button
8.4.3. Solution¶

#%% Interfaces
from abc import ABC, abstractmethod
class Widget(ABC):
@abstractmethod
def render(self) -> None:
raise NotImplementedError
class Button(Widget):
pass
class Textbox(Widget):
pass
class Theme(ABC):
@abstractmethod
def create_button(self) -> Button:
raise NotImplementedError
@abstractmethod
def create_textbox(self) -> Textbox:
raise NotImplementedError
#%% Material Theme
class MaterialButton(Button):
def render(self) -> None:
print('Material Button')
class MaterialTextbox(Textbox):
def render(self) -> None:
print('Material Textbox')
class MaterialTheme(Theme):
def create_button(self) -> Button:
return MaterialButton()
def create_textbox(self) -> Textbox:
return MaterialTextbox()
#%% Flat Theme
class FlatButton(Button):
def render(self) -> None:
print('Flat Button')
class FlatTextbox(Textbox):
def render(self) -> None:
print('Flat Textbox')
class FlatTheme(Theme):
def create_button(self) -> Button:
return FlatButton()
def create_textbox(self) -> Textbox:
return FlatTextbox()
#%% Main
class ContactForm:
def render(self, theme: Theme) -> None:
theme.create_textbox().render()
theme.create_button().render()
if __name__ == '__main__':
theme = FlatTheme()
ContactForm().render(theme)
# Flat Textbox
# Flat Button
theme = MaterialTheme()
ContactForm().render(theme)
# Material Textbox
# Material Button
8.4.4. Assignments¶
"""
* Assignment: DesignPatterns Creational AbstractFactory
* Complexity: easy
* Lines of code: 70 lines
* Time: 21 min
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
>>> 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)