8.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.
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)