Template (#23)

* add documentation

* add code example
This commit is contained in:
Ruidy 2020-10-09 14:07:14 +02:00 committed by GitHub
parent 03cb570b65
commit ca672add42
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 157 additions and 0 deletions

View file

@ -47,6 +47,7 @@ and divided into three groups.
- [Observer](behavioral/observer/README.md)
- [State](behavioral/state/README.md)
- [Strategy](behavioral/strategy/README.md)
- [Template](behavioral/template/README.md)
## Resources

View file

@ -12,3 +12,4 @@ Behavioral design patterns are concerned with algorithms and the assignment of r
- [Observer](observer/README.md)
- [State](state/README.md)
- [Strategy](strategy/README.md)
- [Template](template/README.md)

View file

@ -0,0 +1,44 @@
# Template Method
Template Method is a behavioral design pattern that defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure.
## Problem
Imagine that youre creating a data mining application that analyzes corporate documents. Users feed the app documents in various formats (PDF, DOC, CSV), and it tries to extract meaningful data from these docs in a uniform format.
The first version of the app could work only with DOC files. In the following version, it was able to support CSV files. A month later, you “taught” it to extract data from PDF files.
At some point, you noticed that all three classes have a lot of similar code. While the code for dealing with various data formats was entirely different in all classes, the code for data processing and analysis is almost identical. Wouldn't it be great to get rid of the code duplication, leaving the algorithm structure intact?
There was another problem related to client code that used these classes. It had lots of conditionals that picked a proper course of action depending on the class of the processing object. If all three processing classes had a common interface or a base class, youd be able to eliminate the conditionals in client code and use polymorphism when calling methods on a processing object.
## Solution
The Template Method pattern suggests that you break down an algorithm into a series of steps, turn these steps into methods, and put a series of calls to these methods inside a single template method. The steps may either be `abstract`, or have some default implementation. To use the algorithm, the client is supposed to provide its own subclass, implement all abstract steps, and override some of the optional ones if needed (but not the template method itself).
Lets see how this will play out in our data mining app. We can create a base class for all three parsing algorithms. This class defines a template method consisting of a series of calls to various document-processing steps.
At first, we can declare all steps `abstract`, forcing the subclasses to provide their own implementations for these methods. In our case, subclasses already have all necessary implementations, so the only thing we might need to do is adjust signatures of the methods to match the methods of the superclass.
Now, lets see what we can do to get rid of the duplicate code. It looks like the code for opening/closing files and extracting/parsing data is different for various data formats, so theres no point in touching those methods. However, implementation of other steps, such as analyzing the raw data and composing reports, is very similar, so it can be pulled up into the base class, where subclasses can share that code.
As you can see, weve got two types of steps:
- abstract steps must be implemented by every subclass
- optional steps already have some default implementation, but still can be overridden if needed
Theres another type of step, called hooks. A hook is an optional step with an empty body. A template method would
work even if a hook isnt overridden. Usually, hooks are placed before and after crucial steps of algorithms, providing subclasses with additional extension points for an algorithm.
## How to Implement
1. Analyze the target algorithm to see whether you can break it into steps. Consider which steps are common to all subclasses and which ones will always be unique.
1. Create the abstract base class and declare the template method and a set of abstract methods representing the algorithms steps. Outline the algorithms structure in the template method by executing corresponding steps. Consider making the template method final to prevent subclasses from overriding it.
1. Its okay if all the steps end up being abstract. However, some steps might benefit from having a default implementation. Subclasses dont have to implement those methods.
1. Think of adding hooks between the crucial steps of the algorithm.
1. For each variation of the algorithm, create a new concrete subclass. It must implement all of the abstract steps, but may also override some of the optional ones.

View file

@ -0,0 +1,4 @@
"""
Template Method is a behavioral design pattern that allows you to defines a skeleton of an algorithm in a base class and
let subclasses override the steps without changing the overall algorithms structure.
"""

View file

@ -0,0 +1,57 @@
from abc import ABC, abstractmethod
class AbstractClass(ABC):
"""
The Abstract Class defines a template method that contains a skeleton of
some algorithm, composed of calls to (usually) abstract primitive
operations.
Concrete subclasses should implement these operations, but leave the
template method itself intact.
"""
def template_method(self) -> None:
"""
The template method defines the skeleton of an algorithm.
"""
self.base_operation1()
self.required_operations1()
self.base_operation2()
self.hook1()
self.required_operations2()
self.base_operation3()
self.hook2()
"""These operations already have implementations."""
def base_operation1(self):
print("AbstractClass says: I am doing the bulk of the work")
def base_operation2(self):
print("AbstractClass says: But I let subclasses override some operations")
def base_operation3(self):
print("AbstractClass says: But I am doing the bulk of the work anyway")
"""These operations have to be implemented in subclasses."""
@abstractmethod
def required_operations1(self):
pass
@abstractmethod
def required_operations2(self):
pass
"""
These are "hooks." Subclasses may override them, but it's not mandatory since the hooks already have default (but
empty) implementation. Hooks provide additional extension points in some crucial places of the algorithm.
"""
def hook1(self):
pass
def hook2(self):
pass

View file

@ -0,0 +1,29 @@
from behavioral.template.abstract_class import AbstractClass
class ConcreteClass1(AbstractClass):
"""
Concrete classes have to implement all abstract operations of the base
class. They can also override some operations with a default implementation.
"""
def required_operations1(self):
print("ConcreteClass1 says: Implemented Operation1")
def required_operations2(self):
print("ConcreteClass1 says: Implemented Operation2")
class ConcreteClass2(AbstractClass):
"""
Usually, concrete classes override only a fraction of base class' operations.
"""
def required_operations1(self):
print("ConcreteClass2 says: Implemented Operation1")
def required_operations2(self):
print("ConcreteClass2 says: Implemented Operation2")
def hook1(self):
print("ConcreteCLass2 says: Overridden Hook1")

View file

@ -0,0 +1,21 @@
from behavioral.template.abstract_class import AbstractClass
from behavioral.template.concrete_classes import ConcreteClass1, ConcreteClass2
def client_code(abstract_class: AbstractClass) -> None:
"""
The client code calls the template method to execute the algorithm. Client
code does not have to know the concrete class of an object it works with, as
long as it works with objects through the interface of their base class.
"""
abstract_class.template_method()
if __name__ == "__main__":
print("Same client code can work with different subclasses:")
client_code(ConcreteClass1())
print("")
print("Same client code can work with different subclasses:")
client_code(ConcreteClass2())