factory method example

This commit is contained in:
Ruidy Nemausat 2020-09-11 11:41:18 +02:00
parent c0f0d2e281
commit 5213e5f7a2
10 changed files with 155 additions and 0 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
**__pycache__
*.pyc
.vscode
.DS_Store

View file

@ -1,3 +1,8 @@
# Design Patterns in Python
- [Link](https://refactoring.guru/design-patterns/catalog)
## Table of Content
- [Creational Patterns](creational/README.md)
- [Factory Method](creational/factory-method/factory.md)

3
creational/README.md Normal file
View file

@ -0,0 +1,3 @@
# Creational Patterns
- [Factory Method](factory-method/factory.md)

View file

@ -0,0 +1,34 @@
from abc import ABC, abstractmethod
class ICreator(ABC):
"""
The Creator class declares the factory method that is supposed to return an
object of a Product class. The Creator's subclasses usually provide the
implementation of this method.
"""
@abstractmethod
def factory_method(self):
"""
Note that the Creator may also provide some default implementation of
the factory method.
"""
pass
def some_operation(self) -> str:
"""
Also note that, despite its name, the Creator's primary responsibility
is not creating products. Usually, it contains some core business logic
that relies on Product objects, returned by the factory method.
Subclasses can indirectly change that business logic by overriding the
factory method and returning a different type of product from it.
"""
# Call the factory_method to create a Product object
product = self.factory_method()
# Now, use the product.
result = f"Creator: The same creator's code has just worked with {product.operation()}"
return result

View file

@ -0,0 +1,12 @@
from abc import ABC, abstractmethod
class IProduct(ABC):
"""
The Product interface declares the operations that all concrete products
must implement.
"""
@abstractmethod
def operation(self) -> str:
pass

View file

View file

@ -0,0 +1,25 @@
"""
Concrete Creators override the factory method in order to change the resulting
product's type.
"""
from __future__ import annotations
from ICreator import ICreator
from products import ConcreteProduct1, ConcreteProduct2
class ConcreteCreator1(ICreator):
"""
Note that the signature of the method still uses the abstract product type,
even though the concrete product is actually returned from the method. This
way the Creator can stay independent of concrete product classes.
"""
def factory_method(self) -> ConcreteProduct1:
return ConcreteProduct1()
class ConcreteCreator2(ICreator):
def factory_method(self) -> ConcreteProduct2:
return ConcreteProduct2()

View file

@ -0,0 +1,36 @@
# Factory Method
## Summary
Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.
## Problem
Imagine that youre creating a logistics management application. The first version of your app can only handle transportation by trucks, so the bulk of your code lives inside the `Truck` class.
After a while, your app becomes pretty popular. Each day you receive dozens of requests from sea transportation companies to incorporate sea logistics into the app.
Great news, right? But how about the code? At present, most of your code is coupled to the `Truck` class. Adding `Ships` into the app would require making changes to the entire codebase. Moreover, if later you decide to add another type of transportation to the app, you will probably need to make all of these changes again.
As a result, you will end up with pretty nasty code, riddled with conditionals that switch the apps behavior depending on the class of transportation objects.
## Solution
The Factory Method pattern suggests that you replace direct object construction calls with calls to a special factory method. Objects returned by a factory method are often referred to as products.
## How To Implement
1. Make all products follow the same interface. This interface should declare methods that make sense in every product.
1. Add an empty factory method inside the creator class. The return type of the method should match the common product interface.
1. In the creators code find all references to product constructors. One by one, replace them with calls to the factory method, while extracting the product creation code into the factory method.
You might need to add a temporary parameter to the factory method to control the type of returned product.
At this point, the code of the factory method may look pretty ugly. It may have a large switch operator that picks which product class to instantiate. But dont worry, well fix it soon enough.
1. Now, create a set of creator subclasses for each type of product listed in the factory method. Override the factory method in the subclasses and extract the appropriate bits of construction code from the base method.
1. If there are too many product types and it doesnt make sense to create subclasses for all of them, you can reuse the control parameter from the base class in subclasses.
For instance, imagine that you have the following hierarchy of classes: the base Mail class with a couple of subclasses: AirMail and GroundMail; the Transport classes are Plane, Truck and Train. While the AirMail class only uses Plane objects, GroundMail may work with both Truck and Train objects. You can create a new subclass (say TrainMail) to handle both cases, but theres another option. The client code can pass an argument to the factory method of the GroundMail class to control which product it wants to receive.
1. If, after all of the extractions, the base factory method has become empty, you can make it abstract. If theres something left, you can make it a default behavior of the method.

View file

@ -0,0 +1,21 @@
from creators import ConcreteCreator1, ConcreteCreator2
from ICreator import ICreator
def client_code(creator: ICreator) -> None:
"""
The client code works with an instance of a concrete creator, albeit through
its base interface. As long as the client keeps working with the creator via
the base interface, you can pass it any creator's subclass.
"""
print(f"Client: I'm not aware of the creator's class, but it still works.\n"
f"{creator.some_operation()}", end="")
print("App: Launched with the ConcreteCreator1.")
client_code(ConcreteCreator1())
print("\n")
print("App: Launched with the ConcreteCreator2.")
client_code(ConcreteCreator2())

View file

@ -0,0 +1,15 @@
"""
Concrete Products provide various implementations of the Product interface.
"""
from IProduct import IProduct
class ConcreteProduct1(IProduct):
def operation(self) -> str:
return "{Result of the ConcreteProduct1}"
class ConcreteProduct2(IProduct):
def operation(self) -> str:
return "{Result of the ConcreteProduct2}"