mirror of
https://github.com/rjNemo/design-patterns
synced 2026-06-06 02:26:40 +00:00
factory method example
This commit is contained in:
parent
c0f0d2e281
commit
5213e5f7a2
10 changed files with 155 additions and 0 deletions
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
**__pycache__
|
||||
*.pyc
|
||||
.vscode
|
||||
.DS_Store
|
||||
|
|
@ -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
3
creational/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Creational Patterns
|
||||
|
||||
- [Factory Method](factory-method/factory.md)
|
||||
34
creational/factory-method/ICreator.py
Normal file
34
creational/factory-method/ICreator.py
Normal 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
|
||||
12
creational/factory-method/IProduct.py
Normal file
12
creational/factory-method/IProduct.py
Normal 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
|
||||
0
creational/factory-method/__init__.py
Normal file
0
creational/factory-method/__init__.py
Normal file
25
creational/factory-method/creators.py
Normal file
25
creational/factory-method/creators.py
Normal 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()
|
||||
36
creational/factory-method/factory.md
Normal file
36
creational/factory-method/factory.md
Normal 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 you’re 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 app’s 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 creator’s 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 don’t worry, we’ll 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 doesn’t 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 there’s 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 there’s something left, you can make it a default behavior of the method.
|
||||
21
creational/factory-method/main.py
Normal file
21
creational/factory-method/main.py
Normal 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())
|
||||
15
creational/factory-method/products.py
Normal file
15
creational/factory-method/products.py
Normal 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}"
|
||||
Loading…
Reference in a new issue