8.7. Template Method
EN: Template Method
PL: Metoda szablonowa
Type: class
The Template Method design pattern is a behavioral design pattern that defines the skeleton of an algorithm in a method, deferring some steps to subclasses. It lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
Here's a simple example of the Template Method pattern in Python:
>>> class AbstractClass:
... def template_method(self):
... self.base_operation1()
... self.required_operations1()
... self.base_operation2()
... self.hook1()
...
... def base_operation1(self):
... print("Base operation1")
...
... def base_operation2(self):
... print("Base operation2")
...
... def required_operations1(self):
... pass
...
... def hook1(self):
... pass
...
>>> class ConcreteClass1(AbstractClass):
... def required_operations1(self):
... print("ConcreteClass1 says: Implemented Operation1")
...
... def hook1(self):
... print("ConcreteClass1 says: Overridden Hook1")
...
>>> class ConcreteClass2(AbstractClass):
... def required_operations1(self):
... print("ConcreteClass2 says: Implemented Operation2")
...
>>> concrete_class = ConcreteClass1()
>>> concrete_class.template_method()
Base operation1
ConcreteClass1 says: Implemented Operation1
Base operation2
ConcreteClass1 says: Overridden Hook1
In this example, the AbstractClass provides a method called template_method() that defines a set of steps to execute an algorithm. Some of these steps (required_operations1()) are implemented directly in the AbstractClass, but one (hook1()) is left undefined. The ConcreteClass1 and ConcreteClass2 subclasses provide the implementation for the undefined hook1() method.
8.7.1. Pattern
Bank application with audit trail (all actions)
Record task history
Audits
8.7.2. Problem
Duplicated code
Not enforced to record in audit trail
from dataclasses import dataclass
class AuditTrail:
def record(self) -> None:
print('Audit')
@dataclass
class TransferMoneyTask:
audit_trail: AuditTrail
def execute(self):
self.audit_trail.record()
print('Transfer Money')
@dataclass
class GenerateReportTask:
audit_trail: AuditTrail
def execute(self):
self.audit_trail.record()
print('Generate Report')
if __name__ == '__main__':
audit_trail = AuditTrail()
task = TransferMoneyTask(audit_trail)
task.execute()
# Audit
# Transfer Money
8.7.3. Solution
from abc import ABC, abstractmethod
from dataclasses import dataclass
class AuditTrail:
def record(self) -> None:
print('Audit')
@dataclass
class Task(ABC):
audit_trail: AuditTrail = AuditTrail()
def execute(self) -> None:
self.audit_trail.record()
self.do_execute()
print('Transfer Money')
@abstractmethod
def do_execute(self) -> None:
pass
class GenerateReportTask(Task):
def do_execute(self) -> None:
print('Generate Report')
class TransferMoneyTask(Task):
def do_execute(self) -> None:
print('Transfer Money')
if __name__ == '__main__':
task = TransferMoneyTask()
task.execute()
# Audit
# Transfer Money