-
Book Overview & Buying
-
Table Of Contents
-
Feedback & Rating

Mastering Python Design Patterns
By :

The abstract factory design pattern is a generalization of the factory method. Basically, an abstract factory is a (logical) group of factory methods, where each factory method is responsible for generating a different kind of object.
We are going to discuss some examples, use cases, and a possible implementation.
The abstract factory is used in car manufacturing. The same machinery is used for stamping the parts (doors, panels, hoods, fenders, and mirrors) of different car models. The model that is assembled by the machinery is configurable and easy to change at any time.
In the software category, the factory_boy
(https://github.com/FactoryBoy/factory_boy) package provides an abstract factory implementation for creating Django models in tests. It is used for creating instances of models that support test-specific attributes. This is important because, this way, your tests become readable and you avoid sharing unnecessary code.
Django models are special classes used by the framework to help store and interact with data in the database (tables). See the Django documentation (https://docs.djangoproject.com) for more details.
Since the abstract factory pattern is a generalization of the factory method pattern, it offers the same benefits, it makes tracking an object creation easier, it decouples object creation from object usage, and it gives us the potential to improve the memory usage and performance of our application.
But, a question is raised: How do we know when to use the factory method versus using an abstract factory? The answer is that we usually start with the factory method which is simpler. If we find out that our application requires many factory methods, which it makes sense to combine to create a family of objects, we end up with an abstract factory.
A benefit of the abstract factory that is usually not very visible from a user's point of view when using the factory method is that it gives us the ability to modify the behavior of our application dynamically (at runtime) by changing the active factory method. The classic example is the ability to change the look and feel of an application (for example, Apple-like, Windows-like, and so on) for the user while the application is in use, without the need to terminate it and start it again.
To demonstrate the abstract factory pattern, I will reuse one of my favorite examples, included in the book, Python 3 Patterns, Recipes and Idioms, by Bruce Eckel. Imagine that we are creating a game or we want to include a mini-game as part of our application to entertain our users. We want to include at least two games, one for children and one for adults. We will decide which game to create and launch at runtime, based on user input. An abstract factory takes care of the game creation part.
Let's start with the kid's game. It is called FrogWorld. The main hero is a frog who enjoys eating bugs. Every hero needs a good name, and in our case, the name is given by the user at runtime. The interact_with()
method is used to describe the interaction of the frog with an obstacle (for example, a bug, puzzle, and other frogs) as follows:
class Frog: def __init__(self, name): self.name = name def __str__(self): return self.name def interact_with(self, obstacle): act = obstacle.action() msg = f'{self} the Frog encounters {obstacle} and {act}!' print(msg)
There can be many different kinds of obstacles but for our example, an obstacle can only be a bug. When the frog encounters a bug, only one action is supported. It eats it:
class Bug: def __str__(self): return 'a bug' def action(self): return 'eats it'
The FrogWorld
class is an abstract factory. Its main responsibilities are creating the main character and the obstacle(s) in the game. Keeping the creation methods separate and their names generic (for example, make_character()
and make_obstacle()
) allows us to change the active factory (and therefore the active game) dynamically without any code changes. In a statically typed language, the abstract factory would be an abstract class/interface with empty methods, but in Python, this is not required because the types are checked at runtime (j.mp/ginstromdp). The code is as follows:
class FrogWorld: def __init__(self, name): print(self) self.player_name = name def __str__(self): return '\n\n\t------ Frog World -------' def make_character(self): return Frog(self.player_name) def make_obstacle(self): return Bug()
The WizardWorld game is similar. The only difference is that the wizard battles against monsters such as orks
instead of eating bugs!
Here is the definition of the Wizard
class, which is similar to the Frog
one:
class Wizard: def __init__(self, name): self.name = name def __str__(self): return self.name def interact_with(self, obstacle): act = obstacle.action() msg = f'{self} the Wizard battles against {obstacle} and {act}!' print(msg)
Then, the definition of the Ork
class is as follows:
class Ork: def __str__(self): return 'an evil ork' def action(self): return 'kills it'
We also need to define the WizardWorld
class, similar to the FrogWorld
one that we have discussed; the obstacle, in this case, is an Ork
instance:
class WizardWorld: def __init__(self, name): print(self) self.player_name = name def __str__(self): return '\n\n\t------ Wizard World -------' def make_character(self): return Wizard(self.player_name) def make_obstacle(self): return Ork()
The GameEnvironment
class is the main entry point of our game. It accepts the factory as an input and uses it to create the world of the game. The play()
method initiates the interaction between the created hero and the obstacle, as follows:
class GameEnvironment: def __init__(self, factory): self.hero = factory.make_character() self.obstacle = factory.make_obstacle() def play(self): self.hero.interact_with(self.obstacle)
The validate_age()
function prompts the user to give a valid age. If the age is not valid, it returns a tuple with the first element set to False
. If the age is fine, the first element of the tuple is set to True
and that's the case where we actually care about the second element of the tuple, which is the age given by the user, as follows:
def validate_age(name): try: age = input(f'Welcome {name}. How old are you? ') age = int(age) except ValueError as err: print(f"Age {age} is invalid, please try again...") return (False, age) return (True, age)
Last but not least comes the main()
function. It asks for the user's name and age, and decides which game should be played, given the age of the user, as follows:
def main(): name = input("Hello. What's your name? ") valid_input = False while not valid_input: valid_input, age = validate_age(name) game = FrogWorld if age < 18 else WizardWorld environment = GameEnvironment(game(name)) environment.play()
The summary for the implementation we just discussed (see the complete code in the abstract_factory.py
file) is as follows:
Frog
and Bug
classes for the FrogWorld game.FrogWorld
class, where we use our Frog
and Bug
classes.Wizard
and Ork
classes for the WizardWorld game.WizardWorld
class, where we use our Wizard
and Ork
classes.GameEnvironment
class.validate_age()
function.main()
function, followed by the conventional trick for calling it. The following are the aspects of this function:
GameEnvironment
class.play()
on the environment object to play the gameLet's call this program using the python abstract_factory.py
command, and see some sample output.
The sample output for a teenager is as follows:
The sample output for an adult is as follows:
Try extending the game to make it more complete. You can go as far as you want; create many obstacles, many enemies, and whatever else you like.
Change the font size
Change margin width
Change background colour