Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 28 additions & 73 deletions creational/abstract_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,25 @@

"""
*What is this pattern about?
The Abstract Factory Pattern serves to provide an interface for

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, we 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 AnimalFactory we chose (Dog or Cat)
This works because both Dog/Cat and their factories respect a common
interface (.speak(), get_pet() and get_food()).
Now my application can create pets (and feed them) abstractly and decide later,
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.
The second example allows us to create pets based on the string passed by the
user, using cls.__subclasses__ (the list of sub classes for class cls)
and sub_cls.__name__ to get its name.

*Where is the pattern used practically?

Expand All @@ -30,9 +33,6 @@
Provides a way to encapsulate a group of individual factories.
"""


import six
import abc
import random


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

pet = self.pet_factory.get_pet()
pet = self.pet_factory()
print("We have a lovely {}".format(pet))
print("It says {}".format(pet.speak()))
print("We also have {}".format(self.pet_factory.get_food()))


# Stuff that our factory makes

class Dog(object):

def speak(self):
Expand All @@ -74,80 +71,38 @@ def __str__(self):
return "Cat"


# Factory classes

class DogFactory(object):

def get_pet(self):
return Dog()

def get_food(self):
return "dog food"
# Additional factories:


class CatFactory(object):

def get_pet(self):
return Cat()

def get_food(self):
return "cat food"


# Create the proper family
def get_factory():
# Create a random animal
def random_animal():
"""Let's be dynamic!"""
return random.choice([DogFactory, CatFactory])()


# Implementation 2 of an abstract factory
@six.add_metaclass(abc.ABCMeta)
class Pet(object):

@classmethod
def from_name(cls, name):
for sub_cls in cls.__subclasses__():
if name == sub_cls.__name__.lower():
return sub_cls()

@abc.abstractmethod
def speak(self):
""""""


class Kitty(Pet):
def speak(self):
return "Miao"


class Duck(Pet):
def speak(self):
return "Quak"
return random.choice([Dog, Cat])()


# Show pets with various factories
if __name__ == "__main__":

# A Shop that sells only cats
cat_shop = PetShop(Cat)
cat_shop.show_pet()
print("")

# A shop that sells random animals
shop = PetShop(random_animal)
for i in range(3):
shop = PetShop(get_factory())
shop.show_pet()
print("=" * 20)

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

### OUTPUT ###
# We have a lovely Cat
# It says meow
# We also have cat food
# ====================
#
# We have a lovely Dog
# It says woof
# We also have dog food
# ====================
# We have a lovely Cat
# It says meow
# We also have cat food
# ====================
# kitty: Miao
# duck: Quak
# We have a lovely Cat
# It says meow
# ====================
58 changes: 5 additions & 53 deletions tests/test_abstract_factory.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest
from creational.abstract_factory import PetShop,\
Dog, Cat, DogFactory, CatFactory, Pet
from creational.abstract_factory import PetShop, Dog
try:
from unittest.mock import patch
except ImportError:
Expand All @@ -12,54 +11,7 @@
class TestPetShop(unittest.TestCase):

def test_dog_pet_shop_shall_show_dog_instance(self):
f = DogFactory()
with patch.object(f, 'get_pet') as mock_f_get_pet,\
patch.object(f, 'get_food') as mock_f_get_food:
ps = PetShop(f)
ps.show_pet()
self.assertEqual(mock_f_get_pet.call_count, 1)
self.assertEqual(mock_f_get_food.call_count, 1)

def test_cat_pet_shop_shall_show_cat_instance(self):
f = CatFactory()
with patch.object(f, 'get_pet') as mock_f_get_pet,\
patch.object(f, 'get_food') as mock_f_get_food:
ps = PetShop(f)
ps.show_pet()
self.assertEqual(mock_f_get_pet.call_count, 1)
self.assertEqual(mock_f_get_food.call_count, 1)


class TestCat(unittest.TestCase):

@classmethod
def setUpClass(cls):
cls.c = Cat()

def test_cat_shall_meow(cls):
cls.assertEqual(cls.c.speak(), 'meow')

def test_cat_shall_be_printable(cls):
cls.assertEqual(str(cls.c), 'Cat')


class TestDog(unittest.TestCase):

@classmethod
def setUpClass(cls):
cls.d = Dog()

def test_dog_shall_woof(cls):
cls.assertEqual(cls.d.speak(), 'woof')

def test_dog_shall_be_printable(cls):
cls.assertEqual(str(cls.d), 'Dog')


class PetTest(unittest.TestCase):

def test_from_name(self):
test_cases = [("kitty", "Miao"), ("duck", "Quak")]
for name, expected_speech in test_cases:
pet = Pet.from_name(name)
self.assertEqual(pet.speak(), expected_speech)
dog_pet_shop = PetShop(Dog)
with patch.object(Dog, 'speak') as mock_Dog_speak:
dog_pet_shop.show_pet()
self.assertEqual(mock_Dog_speak.call_count, 1)