Template Method Design Pattern
Video Lecture
Overview
In the Template Method pattern, you create an abstract class (template) that contains a Template Method that is a series of instructions that are a combination of abstract and hook methods.
Abstract methods need to be overridden in the subclasses that extend the abstract (template) class.
Hook methods normally have empty bodies in the abstract class. Subclasses can optionally override the hook methods to create custom implementations.
So, what you have, is an abstract class, with several types of methods, being the main template method, and a combination of abstract and/or hooks, that can be extended by different subclasses that all have the option of customizing the behavior of the template class without changing its underlying algorithm structure.
Template methods are useful to help you factor out common behavior within your library classes.
Note that this pattern describes the behavior of a method and how its inner method calls behave.
Hooks are default behavior and can be overridden. They are normally empty by default.
Abstract methods, must be overridden in the concrete class that extends the template class.
Terminology
- Abstract Class: Defines the template method and the primitive steps as abstract and/or hook methods.
- Concrete Class: A subclass that extends some or all of the abstract class primitive methods.
Template Method UML Diagram
Source Code
Note that in both the concrete classes in this concept example, the template_method()
was not overridden since it was already inherited. Only the primitives (abstract or hooks) were optionally overridden.
To create an empty abstract method in your abstract class, that must be overridden in a subclass, then use the ABCMeta @abstractmethod
decorator.
./template/template_concept.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 | # pylint: disable=too-few-public-methods
"The Template Method Pattern Concept"
from abc import ABCMeta, abstractmethod
class AbstractClass(metaclass=ABCMeta):
"A template class containing a template method and primitive methods"
@staticmethod
def step_one():
"""
Hooks are normally empty in the abstract class. The
implementing class can optionally override providing a custom
implementation
"""
@staticmethod
@abstractmethod
def step_two():
"""
An abstract method that must be overridden in the implementing
class. It has been given `@abstractmethod` decorator so that
pylint shows the error.
"""
@staticmethod
def step_three():
"""
Hooks can also contain default behavior and can be optionally
overridden
"""
print("Step Three is a hook that prints this line by default.")
@classmethod
def template_method(cls):
"""
This is the template method that the subclass will call.
The subclass (implementing class) doesn't need to override this
method since it has would have already optionally overridden
the following methods with its own implementations
"""
cls.step_one()
cls.step_two()
cls.step_three()
class ConcreteClassA(AbstractClass):
"A concrete class that only overrides step two"
@staticmethod
def step_two():
print("Class_A : Step Two (overridden)")
class ConcreteClassB(AbstractClass):
"A concrete class that only overrides steps one, two and three"
@staticmethod
def step_one():
print("Class_B : Step One (overridden)")
@staticmethod
def step_two():
print("Class_B : Step Two. (overridden)")
@staticmethod
def step_three():
print("Class_B : Step Three. (overridden)")
# The Client
CLASS_A = ConcreteClassA()
CLASS_A.template_method()
CLASS_B = ConcreteClassB()
CLASS_B.template_method()
|
Output
python ./template/template_concept.py
Class_A : Step Two (overridden)
Step Three is a hook that prints this line by default.
Class_B : Step One (overridden)
Class_B : Step Two. (overridden)
Class_B : Step Three. (overridden)
SBCODE Editor
Template Method Example Use Case
In the example use case, there is an AbstractDocument
with several methods, some are optional and others must be overridden.
The document will be written out in two different formats.
Depending on the concrete class used, the text()
method will wrap new lines with <p>
tags and the print()
method will format text with tabs, or include HTML tags.
Template Method Use Case UML Diagram
Source Code
./template/client.py
| "The Template Pattern Use Case Example"
from text_document import TextDocument
from html_document import HTMLDocument
TEXT_DOCUMENT = TextDocument()
TEXT_DOCUMENT.create_document("Some Text")
HTML_DOCUMENT = HTMLDocument()
HTML_DOCUMENT.create_document("Line 1\nLine 2")
|
./template/abstract_document.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 | "An abstract document containing a combination of hooks and abstract methods"
from abc import ABCMeta, abstractmethod
class AbstractDocument(metaclass=ABCMeta):
"A template class containing a template method and primitive methods"
@staticmethod
@abstractmethod
def title(document):
"must implement"
@staticmethod
def description(document):
"optional"
@staticmethod
def author(document):
"optional"
@staticmethod
def background_colour(document):
"optional with a default behavior"
document["background_colour"] = "white"
@staticmethod
@abstractmethod
def text(document, text):
"must implement"
@staticmethod
def footer(document):
"optional"
@staticmethod
def print(document):
"optional with a default behavior"
print("----------------------")
for attribute in document:
print(f"{attribute}\t: {document[attribute]}")
print()
@classmethod
def create_document(cls, text):
"The template method"
_document = {}
cls.title(_document)
cls.description(_document)
cls.author(_document)
cls.background_colour(_document)
cls.text(_document, text)
cls.footer(_document)
cls.print(_document)
|
./template/text_document.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | "A text document concrete class of AbstractDocument"
from abstract_document import AbstractDocument
class TextDocument(AbstractDocument):
"Prints out a text document"
@staticmethod
def title(document):
document["title"] = "New Text Document"
@staticmethod
def text(document, text):
document["text"] = text
@staticmethod
def footer(document):
document["footer"] = "-- Page 1 --"
|
./template/html_document.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 | "A HTML document concrete class of AbstractDocument"
from abstract_document import AbstractDocument
class HTMLDocument(AbstractDocument):
"Prints out a HTML formatted document"
@staticmethod
def title(document):
document["title"] = "New HTML Document"
@staticmethod
def text(document, text):
"Putting multiple lines into there own p tags"
lines = text.splitlines()
markup = ""
for line in lines:
markup = markup + " <p>" + f"{line}</p>\n"
document["text"] = markup[:-1]
@staticmethod
def print(document):
"overriding print to output with html tags"
print("<html>")
print(" <head>")
for attribute in document:
if attribute in ["title", "description", "author"]:
print(
f" <{attribute}>{document[attribute]}"
f"</{attribute}>"
)
if attribute == "background_colour":
print(" <style>")
print(" body {")
print(
f" background-color: "
f"{document[attribute]};")
print(" }")
print(" </style>")
print(" </head>")
print(" <body>")
print(f"{document['text']}")
print(" </body>")
print("</html>")
|
Output
python ./template/client.py
----------------------
title : New Text Document
background_colour : white
text : Some Text
footer : -- Page 1 --
<html>
<head>
<title>New HTML Document</title>
<style>
body {
background-color: white;
}
</style>
</head>
<body>
<p>Line 1</p>
<p>Line 2</p>
</body>
</html>
SBCODE Editor
Summary
-
The Template method defines an algorithm in terms of abstract operations and subclasses override some or all of the methods to create concrete behaviors.
-
Abstract methods must be overridden in the subclasses that extend the abstract class.
-
Hook Methods usually have empty bodies in the super class but can be optionally overridden in the subclass.
-
If a class contains many conditional statements, consider converting it to use the Template Method pattern.