There are two main benefits of the Abstract Factory pattern:
1) Decouple the client code
The point of the Abstract Factory pattern is to decouple the client (in this case PetStore
) from the concrete classes it creates (in this case Dog
).
Image you had a different family of pets - cats, for example. Using the Abstract factory pattern, you can create a different factory that produces Cat
objects. The key insight here is that both factories share the same interface - i.e. get_pet()
and get_food()
:
class Cat:
def speak(self): return "Meow!"
def __str__(self): return "Cat"
class CatFactory:
def get_pet(self): return Cat()
def get_food(self): return "Cat Food!"
Now, because of the common interface between factories, you only need to chance one line of code to have the client act on cats instead of dogs:
factory = CatFactory()
The PetStore
class itself does not need to be changed.
2) Enforce a relationship between a family of related classes
You could argue that, instead of passing in a DogFactory
or CatFactory
, why not just pass in a Dog
or a Cat
? And this is where your example code fails to show the power of the Abstract Factory pattern. The pattern really shines when there is a whole family of related classes that go together. In your example, there is only the dog itself, and the dog food. But imagine you also had a dog bed, a dog collar, a dog toy, etc. If this were the case, and you were not using the Abstract Factory pattern, then you might write code like this:
pet = Dog()
food = DogFood()
bed = DogBed()
collar = DogCollar()
toy = DogToy()
shop = PetStore(pet, food, bed, collar, toy)
This code is verbose and inflexible. There also is a danger that you might accidentally pass a cat toy in with dog products.
Instead, using an AbstractFactory, the code becomes trivial and the relationship between the families of classes is enforced:
factory = DogFactory()
shop = PetStore(factory) # PetStore calls get_*(), and is guaranteed
# to only get dog products