forked from faif/python-patterns
-
Notifications
You must be signed in to change notification settings - Fork 0
/
abstract_factory.py
121 lines (87 loc) · 3 KB
/
abstract_factory.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
"""
*What is this pattern about?
In Java and other languages, the Abstract Factory Pattern serves to provide an interface for
creating related/dependent objects without need to specify their
actual class.
The idea is to abstract the creation of objects depending on business
logic, platform choice, etc.
In Python, the interface we use is simply a callable, which is "builtin" interface
in Python, and in normal circumstances we can simply use the class itself as
that callable, because classes are first class objects in Python.
*What does this example do?
This particular implementation abstracts the creation of a pet and
does so depending on the factory we chose (Dog or Cat, or random_animal)
This works because both Dog/Cat and random_animal respect a common
interface (callable for creation and .speak()).
Now my application can create pets abstractly and decide later,
based on my own criteria, dogs over cats.
*Where is the pattern used practically?
*References:
https://sourcemaking.com/design_patterns/abstract_factory
http://ginstrom.com/scribbles/2007/10/08/design-patterns-python-style/
*TL;DR
Provides a way to encapsulate a group of individual factories.
"""
import random
from typing import Type
class Pet:
def __init__(self, name: str) -> None:
self.name = name
def speak(self) -> None:
raise NotImplementedError
def __str__(self) -> str:
raise NotImplementedError
class Dog(Pet):
def speak(self) -> None:
print("woof")
def __str__(self) -> str:
return f"Dog<{self.name}>"
class Cat(Pet):
def speak(self) -> None:
print("meow")
def __str__(self) -> str:
return f"Cat<{self.name}>"
class PetShop:
"""A pet shop"""
def __init__(self, animal_factory: Type[Pet]) -> None:
"""pet_factory is our abstract factory. We can set it at will."""
self.pet_factory = animal_factory
def buy_pet(self, name: str) -> Pet:
"""Creates and shows a pet using the abstract factory"""
pet = self.pet_factory(name)
print(f"Here is your lovely {pet}")
return pet
# Additional factories:
# Create a random animal
def random_animal(name: str) -> Pet:
"""Let's be dynamic!"""
return random.choice([Dog, Cat])(name)
# Show pets with various factories
def main() -> None:
"""
# A Shop that sells only cats
>>> cat_shop = PetShop(Cat)
>>> pet = cat_shop.buy_pet("Lucy")
Here is your lovely Cat<Lucy>
>>> pet.speak()
meow
# A shop that sells random animals
>>> shop = PetShop(random_animal)
>>> for name in ["Max", "Jack", "Buddy"]:
... pet = shop.buy_pet(name)
... pet.speak()
... print("=" * 20)
Here is your lovely Cat<Max>
meow
====================
Here is your lovely Dog<Jack>
woof
====================
Here is your lovely Dog<Buddy>
woof
====================
"""
if __name__ == "__main__":
random.seed(1234) # for deterministic doctest outputs
import doctest
doctest.testmod()