Skip to content

Commit d68190d

Browse files
committed
Simplified the Abstract Factory Pattern to be Pythonic
Once we appreciate that: 1) classes are their own factories and 2) classes are first class objects in Python, this pattern is massively simpler in idiomatic Python. Additional factory classes are not needed. This change also removes the `get_food` method because: 1) This violates the single responsibility principle of the factory 2) It obscures the main point. If we want an association between animals and what food they eat we should either: 1) make it part of the Pet interface 2) or, have another callable which can make this decision (presumably it should be passed the animal instance in order to do so). Or, if we really need it, create an interface that does both creation and feeding as before, but to have that in the this pattern obscures the simplicity of how this can and should be implemented in Python. The second implementation was also deleted. This simply looks up the classes using a string name, which is pointless when you can just use the class itself (why use `"kitty"` when you can use `Cat`).
1 parent ac79516 commit d68190d

File tree

1 file changed

+25
-71
lines changed

1 file changed

+25
-71
lines changed

creational/abstract_factory.py

Lines changed: 25 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,25 @@
33

44
"""
55
*What is this pattern about?
6-
The Abstract Factory Pattern serves to provide an interface for
6+
7+
In Java and other languages, the Abstract Factory Pattern serves to provide an interface for
78
creating related/dependent objects without need to specify their
89
actual class.
10+
911
The idea is to abstract the creation of objects depending on business
1012
logic, platform choice, etc.
1113
14+
In Python, we interface we use is simply a callable, which is "builtin" interface
15+
in Python, and in normal circumstances we can simply use the class itself as
16+
that callable, because classes are first class objects in Python.
17+
1218
*What does this example do?
1319
This particular implementation abstracts the creation of a pet and
14-
does so depending on the AnimalFactory we chose (Dog or Cat)
15-
This works because both Dog/Cat and their factories respect a common
16-
interface (.speak(), get_pet() and get_food()).
17-
Now my application can create pets (and feed them) abstractly and decide later,
20+
does so depending on the factory we chose (Dog or Cat, or random_animal)
21+
This works because both Dog/Cat and random_animal respect a common
22+
interface (callable for creation and .speak()).
23+
Now my application can create pets abstractly and decide later,
1824
based on my own criteria, dogs over cats.
19-
The second example allows us to create pets based on the string passed by the
20-
user, using cls.__subclasses__ (the list of sub classes for class cls)
21-
and sub_cls.__name__ to get its name.
2225
2326
*Where is the pattern used practically?
2427
@@ -30,9 +33,6 @@
3033
Provides a way to encapsulate a group of individual factories.
3134
"""
3235

33-
34-
import six
35-
import abc
3636
import random
3737

3838

@@ -48,14 +48,11 @@ def __init__(self, animal_factory=None):
4848
def show_pet(self):
4949
"""Creates and shows a pet using the abstract factory"""
5050

51-
pet = self.pet_factory.get_pet()
51+
pet = self.pet_factory()
5252
print("We have a lovely {}".format(pet))
5353
print("It says {}".format(pet.speak()))
54-
print("We also have {}".format(self.pet_factory.get_food()))
5554

5655

57-
# Stuff that our factory makes
58-
5956
class Dog(object):
6057

6158
def speak(self):
@@ -74,80 +71,37 @@ def __str__(self):
7471
return "Cat"
7572

7673

77-
# Factory classes
78-
79-
class DogFactory(object):
80-
81-
def get_pet(self):
82-
return Dog()
83-
84-
def get_food(self):
85-
return "dog food"
86-
87-
88-
class CatFactory(object):
89-
90-
def get_pet(self):
91-
return Cat()
92-
93-
def get_food(self):
94-
return "cat food"
95-
74+
# Additional factories:
9675

97-
# Create the proper family
98-
def get_factory():
76+
# Create a random animal
77+
def random_animal():
9978
"""Let's be dynamic!"""
100-
return random.choice([DogFactory, CatFactory])()
101-
102-
103-
# Implementation 2 of an abstract factory
104-
@six.add_metaclass(abc.ABCMeta)
105-
class Pet(object):
106-
107-
@classmethod
108-
def from_name(cls, name):
109-
for sub_cls in cls.__subclasses__():
110-
if name == sub_cls.__name__.lower():
111-
return sub_cls()
112-
113-
@abc.abstractmethod
114-
def speak(self):
115-
""""""
116-
117-
118-
class Kitty(Pet):
119-
def speak(self):
120-
return "Miao"
121-
122-
123-
class Duck(Pet):
124-
def speak(self):
125-
return "Quak"
79+
return random.choice([Dog, Cat])()
12680

12781

12882
# Show pets with various factories
12983
if __name__ == "__main__":
84+
85+
# A Shop that sells only cats
86+
cat_shop = PetShop(Cat)
87+
cat_shop.show_pet()
88+
print("")
89+
90+
# A shop that sells random animals
91+
shop = PetShop(random_animal)
13092
for i in range(3):
131-
shop = PetShop(get_factory())
13293
shop.show_pet()
13394
print("=" * 20)
13495

135-
for name0 in ["kitty", "duck"]:
136-
pet = Pet.from_name(name0)
137-
print("{}: {}".format(name0, pet.speak()))
13896

13997
### OUTPUT ###
14098
# We have a lovely Cat
14199
# It says meow
142-
# We also have cat food
100+
#
143101
# ====================
144102
# We have a lovely Dog
145103
# It says woof
146-
# We also have dog food
147104
# ====================
148105
# We have a lovely Cat
149106
# It says meow
150-
# We also have cat food
151107
# ====================
152-
# kitty: Miao
153-
# duck: Quak

0 commit comments

Comments
 (0)