Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Mastering Python Design Patterns

You're reading from   Mastering Python Design Patterns A guide to creating smart, efficient, and reusable software

Arrow left icon
Product type Paperback
Published in Aug 2018
Publisher
ISBN-13 9781788837484
Length 248 pages
Edition 2nd Edition
Languages
Arrow right icon
Authors (2):
Arrow left icon
Sakis Kasampalis Sakis Kasampalis
Author Profile Icon Sakis Kasampalis
Sakis Kasampalis
Kamon Ayeva Kamon Ayeva
Author Profile Icon Kamon Ayeva
Kamon Ayeva
Arrow right icon
View More author details
Toc

Table of Contents (17) Chapters Close

Preface 1. The Factory Pattern FREE CHAPTER 2. The Builder Pattern 3. Other Creational Patterns 4. The Adapter Pattern 5. The Decorator Pattern 6. The Bridge Pattern 7. The Facade Pattern 8. Other Structural Patterns 9. The Chain of Responsibility Pattern 10. The Command Pattern 11. The Observer Pattern 12. The State Pattern 13. Other Behavioral Patterns 14. The Observer Pattern in Reactive Programming 15. Microservices and Patterns for the Cloud 16. Other Books You May Enjoy

The abstract factory

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.

Real-world examples

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.

Use cases

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.

Implementing the abstract factory pattern

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:

  1. We define the Frog and Bug classes for the FrogWorld game.
  2. We add the FrogWorld class, where we use our Frog and Bug classes.
  1. We define the Wizard and Ork classes for the WizardWorld game.
  2. We add the WizardWorld class, where we use our Wizard and Ork classes.
  3. We define the GameEnvironment class.
  4. We add the validate_age() function.
  5. Finally, we have the main() function, followed by the conventional trick for calling it. The following are the aspects of this function:
    • We get the user's input for name and age
    • We decide which game class to use based on the user's age
    • We instantiate the right game class, and then the GameEnvironment class
    • We call .play() on the environment object to play the game

Let'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.

You have been reading a chapter from
Mastering Python Design Patterns - Second Edition
Published in: Aug 2018
Publisher:
ISBN-13: 9781788837484
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image