Skip to content

Template Method Design Pattern

Overview

...Refer to Book or Videos for extra content.

Terminology

...Refer to Book or Videos for extra content.

Template Method UML Diagram

Template Method UML Diagram

Source Code

...Refer to Book or Videos for extra content.

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

// 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

...Refer to Book or Videos for extra content.

Template Method Use Case UML Diagram

Template Method Use Case UML Diagram

Source Code

./src/template/client.ts

// 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

// 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

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

// 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

...Refer to Book or Videos for extra content.