diff --git a/creational/abstract_factory.py b/creational/abstract_factory.py index 564bcca96..6c781b86a 100644 --- a/creational/abstract_factory.py +++ b/creational/abstract_factory.py @@ -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? @@ -30,9 +33,6 @@ Provides a way to encapsulate a group of individual factories. """ - -import six -import abc import random @@ -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): @@ -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 +# ==================== diff --git a/tests/test_abstract_factory.py b/tests/test_abstract_factory.py index 8ad0838e8..0e64c7127 100644 --- a/tests/test_abstract_factory.py +++ b/tests/test_abstract_factory.py @@ -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: @@ -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)