Skip to content

Template Method Design Pattern

Video Lecture

Section Video Links
Template Method Pattern Template Method Template Method Pattern  
Template Method Use Case Template Method Use Case Template Method Use Case  

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

Template Method UML Diagram

Source Code

Note that in both the concrete classes in this concept example, the templateMethod() was not overridden since it was already inherited. Only the primitives (abstract or hooks) were optionally overridden.

./src/template/template-method-concept.ts

 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
// The Template Method Pattern Concept"

abstract class AbstractClass {
    // A template class containing a template method and primitive methods

    stepOne(): void {
        // Hooks are normally empty in the abstract class. The
        // implementing class can optionally override providing a custom
        // implementation
    }

    abstract stepTwo(): void
    // An abstract method that must be overridden in the implementing
    // class. It has been given `@abstractmethod` decorator so that
    // pylint shows the error

    stepThree(): void {
        // Hooks can also contain default behavior and can be optionally
        // overridden
        console.log(
            'Step Three is a hook that prints this line by default.'
        )
    }

    templateMethod() {
        // 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
        this.stepOne()
        this.stepTwo()
        this.stepThree()
    }
}

class ConcreteClassA extends AbstractClass {
    // A concrete class that only overrides step two"
    stepTwo() {
        console.log('Class_A : Step Two (overridden)')
    }
}

class ConcreteClassB extends AbstractClass {
    // A concrete class that only overrides steps one, two and three"
    stepOne() {
        console.log('Class_B : Step One (overridden)')
    }

    stepTwo() {
        console.log('Class_B : Step Two. (overridden)')
    }

    stepThree() {
        console.log('Class_B : Step Three. (overridden)')
    }
}

// The Client
const CLASS_A = new ConcreteClassA()
CLASS_A.templateMethod()

const CLASS_B = new ConcreteClassB()
CLASS_B.templateMethod()

Output

node ./dist/template-method/template-method-concept.js
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)

Template Method 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

Template Method Use Case UML Diagram

Source Code

./src/template/client.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// The Template Pattern Use Case Example

import TextDocument from './text-document'
import HTMLDocument from './html-document'

const TEXT_DOCUMENT = new TextDocument()
TEXT_DOCUMENT.createDocument('Some Text')

const HTML_DOCUMENT = new HTMLDocument()
HTML_DOCUMENT.createDocument('Line 1\nLine 2')

./src/template/abstract-document.ts

 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
// An abstract document containing a combination of hooks and abstract methods

export interface Document {
    [id: string]: string
}

export abstract class AbstractDocument {
    // A template class containing a template method and primitive methods

    document: Document = {}

    abstract title(document: Document): void
    // Must implement

    description?(document: Document): void
    // Optional

    author?(document: Document): void
    // Optional

    backgroundColour(document: Document): void {
        // Optional with a default behavior
        document['bg-col'] = 'white'
    }

    abstract text(document: Document, text: string): void
    // Must implement

    footer?(document: Document): void
    // Optional

    print(document: Document): void {
        // Optional with a default behavior"
        console.log('----------------------')
        Object.keys(document).forEach((attribute: string) => {
            console.log(`${attribute}\t: ${document[attribute]}`)
        })
        console.log()
    }

    createDocument(text: string): void {
        // The template method
        this.title(this.document)
        if (this.description) this.description(this.document)
        if (this.author) this.author(this.document)
        this.backgroundColour(this.document)
        this.text(this.document, text)
        if (this.footer) this.footer(this.document)
        this.print(this.document)
    }
}

./src/template/text-document.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import { Document, AbstractDocument } from './abstract-document'

export default class TextDocument extends AbstractDocument {
    title(document: Document): void {
        document['title'] = 'New Text Document'
    }

    text(document: Document, text: string): void {
        document['text'] = text
    }

    footer(document: Document): void {
        document['footer'] = '-- Page 1 --'
    }
}

./src/template/html-document.ts

 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
// A HTML document concrete class of AbstractDocument

import { Document, AbstractDocument } from './abstract-document'

export default class HTMLDocument extends AbstractDocument {
    title(document: Document): void {
        document['title'] = 'New HTML Document'
    }

    text(document: Document, text: string): void {
        // Putting multiple lines into there own p tags
        const lines = text.split('\n')
        let markup = ''
        lines.forEach((line) => {
            markup = markup + '    <p>' + line + '</p>\n'
            document['text'] = markup.substring(0, markup.length - 1)
        })
    }

    print(document: Document): void {
        // overriding print to output with html tags
        console.log('<html>')
        console.log('  <head>')
        Object.keys(document).forEach((attribute: string) => {
            if (
                ['title', 'description', 'author'].indexOf(attribute) > -1
            ) {
                console.log(
                    `    <${attribute}>${document[attribute]}</${attribute}>`
                )
            }
            if (attribute === 'bg-col') {
                console.log('    <style>')
                console.log('      body {')
                console.log(
                    `        background-color: ${document[attribute]};`
                )
                console.log('      }')
                console.log('    </style>')
            }
        })
        console.log('  </head>')
        console.log('  <body>')
        console.log(`${document['text']}`)
        console.log('  </body>')
        console.log('</html>')
    }
}

Output

node ./dist/template-method/client.js
----------------------
title   : New Text Document
bg-col  : 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>

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.