Skip to content

Visitor Design Pattern

Video Lecture

Section Video Links
Visitor Pattern Visitor Visitor Pattern  
Visitor Use Case Visitor Use Case Visitor Use Case  

Overview

Your object structure inside an application may be complicated and varied. A good example is what could be created using the Composite structure.

The objects that make up the hierarchy of objects, can be anything and most likely complicated to modify as your application grows.

Instead, when designing the objects in your application that may be structured in a hierarchical fashion, you can allow them to implement a Visitor interface.

The Visitor interface describes an accept() method that a different object, called a Visitor, will use in order to traverse through the existing object hierarchy and read the internal attributes of an object.

The Visitor pattern is useful when you want to analyze, or reproduce an alternative object hierarchy without implementing extra code in the object classes, except for the original requirements set by implementing the Visitor interface.

Similar to the template pattern it could be used to output different versions of a document but more suited to objects that may be members of a hierarchy.

Terminology

  • Visitor Interface: An interface for the Concrete Visitors.
  • Concrete Visitor: The Concrete Visitor will traverse the hierarchy of elements.
  • Concrete Element: (Part) An object that will be visited. An application will contain a variable number of Elements/Parts that can be structured in any particular hierarchy.
  • Visitable Interface: The interface that elements/parts should implement, that describes the accept() method that will allow them to be visited (traversed).

Visitor UML Diagram

Visitor Pattern UML Diagram

Source Code

In the concept code below, a hierarchy of any object is created. It is similar to a simplified composite. The objects of Part can also contain a hierarchy of sub elements/parts.

The Part class could also consist of many variations, but this example uses only one.

Rather than writing specific code inside all these elements/parts every time I wanted to handle a new custom operation, I can implement the IVisitable interface and create the accept() method that allows the custom Visitor to pass through it and access the Elements/Parts internal attributes instead.

Two different Visitor classes are created, PrintPartNamesVisitor and CalculatePartTotalsVisitor. They are instantiated and passed through the existing Object hierarchy using the same IVisitable interface.

./src/visitor/visitor-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
64
65
66
67
68
69
70
71
72
73
// The Visitor Pattern Concept

interface IVisitor {
    // An interface that custom Visitors should implement
    visit(part: Part): void
}

interface IVisitable {
    // An interface the concrete objects should implement that allows
    // the visitor to traverse a hierarchical structure of objects
    accept(visitor: IVisitor): void
}

class Part implements IVisitable {
    // a.k.a Element. An Object that can be part of any hierarchy
    name: string
    value: number
    parts: Set<Part>

    constructor(name: string, value: number, parent?: Part) {
        this.name = name
        this.value = value
        this.parts = new Set()
        if (parent) {
            parent.parts.add(this)
        }
    }

    accept(visitor: IVisitor) {
        // required by the Visitor that will traverse
        this.parts.forEach((part) => {
            part.accept(visitor)
        })
        visitor.visit(this)
    }
}

// The Client
// Creating an example object hierarchy.
const Part_A = new Part('A', 101)
const Part_B = new Part('B', 305, Part_A)
const Part_C = new Part('C', 185, Part_A)
const Part_D = new Part('D', -30, Part_B)

// Now Rather than changing the Part class to support custom
// operations, we can utilise the accept method that was
// implemented in the Part class because of the addition of
// the IVisitable interface

class PrintPartNamesVisitor implements IVisitor {
    // Create a visitor that prints the part names
    visit(part: Part) {
        console.log(part.name)
    }
}

// Using the PrintPartNamesVisitor to traverse the object hierarchy
Part_A.accept(new PrintPartNamesVisitor())

class CalculatePartTotalsVisitor implements IVisitor {
    // Create a visitor that totals the part values
    totalValue = 0

    visit(part: Part) {
        this.totalValue += part.value
    }
}

// Using the CalculatePartTotalsVisitor to traverse the
// object hierarchy
const CALC_TOTALS_VISITOR = new CalculatePartTotalsVisitor()
Part_A.accept(CALC_TOTALS_VISITOR)
console.log(CALC_TOTALS_VISITOR.totalValue)

Output

node ./dist/visitor/visitor-concept.js
D
B
C
A
561

Visitor Use Case

In the example, the client creates a car with parts.

The car and parts inherit an abstract car parts class with predefined property getters and setters.

Instead of creating methods in the car parts classes and abstract class that run bespoke methods, the car parts can all implement the IVisitor interface.

This allows for the later creation of Visitor objects to run specific tasks on the existing hierarchy of objects.

Visitor Example UML Diagram

Visitor Pattern Use Case UML Diagram

Source Code

./src/visitor/client.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
64
65
66
67
68
69
70
71
72
73
// The Visitor Pattern Use Case Example

import AbstractCarPart from './abstract-car-part'
import IVisitor from './ivisitor'

class CarBody extends AbstractCarPart {
    // A part of the car
}

class Engine extends AbstractCarPart {
    // A part of the car
}

class Wheel extends AbstractCarPart {
    // A part of the car
}

class Car extends AbstractCarPart {
    // A Car with parts
    #parts: AbstractCarPart[]

    constructor(name: string) {
        super(name)
        this.#parts = [
            new CarBody('Utility Body', 'ABC-123-21', 1001),
            new Engine('V8 engine', 'DEF-456-21', 2555),
            new Wheel('FrontLeft', 'GHI-789FL-21', 136),
            new Wheel('FrontRight', 'GHI-789FR-21', 136),
            new Wheel('BackLeft', 'GHI-789BL-21', 152),
            new Wheel('BackRight', 'GHI-789BR-21', 152),
        ]
    }

    accept(visitor: IVisitor) {
        this.#parts.forEach((part) => {
            part.accept(visitor)
        })
        visitor.visit(this)
    }
}

class PrintPartsVisitor implements IVisitor {
    // Print out the part name and sku
    visit(abstractCarPart: AbstractCarPart) {
        if (abstractCarPart.sku !== undefined) {
            console.log(
                `${abstractCarPart.name}\t:${abstractCarPart.sku}\t:${abstractCarPart.price}`
            )
        }
    }
}

class TotalPriceVisitor implements IVisitor {
    // Print out the total cost of the parts in the car
    totalPrice = 0

    visit(abstractCarPart: AbstractCarPart) {
        if (abstractCarPart.price !== undefined) {
            this.totalPrice += abstractCarPart.price as number
        }
    }
}

// The Client
const CAR = new Car('DeLorean')

// Print out the part name and sku using the PrintPartsVisitor
CAR.accept(new PrintPartsVisitor())

// Calculate the total prince of the parts using the TotalPriceVisitor
const TOTAL_PRICE_VISITOR = new TotalPriceVisitor()
CAR.accept(TOTAL_PRICE_VISITOR)
console.log(`Total Price = ${TOTAL_PRICE_VISITOR.totalPrice}`)

./src/visitor/abstract-car-part.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
import IVisitable from './ivisitable'
import IVisitor from './ivisitor'

export default abstract class AbstractCarPart implements IVisitable {
    // The Abstract Car Part
    #name: string
    #sku: string | undefined
    #price: number | undefined

    constructor(name: string, sku?: string, price?: number) {
        this.#name = name
        this.#sku = sku
        this.#price = price
    }

    public get name(): string {
        return this.#name
    }

    public set name(value: string) {
        this.#name = value
    }

    public get sku(): string | undefined {
        return this.#sku
    }

    public set sku(value: string | undefined) {
        this.#sku = value
    }

    public get price(): number | undefined {
        return this.#price
    }

    public set price(value: number | undefined) {
        this.#price = value
    }

    accept(visitor: IVisitor): void {
        visitor.visit(this)
    }
}

./src/visitor/ivisitable.ts

1
2
3
4
5
6
7
import IVisitor from './ivisitor'

export default interface IVisitable {
    // An interface the concrete objects should implement that allows
    // the visitor to traverse a hierarchical structure of objects
    accept(visitor: IVisitor): void
}

./src/visitor/ivisitor.ts

1
2
3
4
5
6
import AbstractCarPart from './abstract-car-part'

export default interface IVisitor {
    // An interface that custom Visitors should implement
    visit(abstractCarPart: AbstractCarPart): void
}

Output

node ./dist/visitor/client.js
Utility Body    :ABC-123-21     :1001
V8 engine       :DEF-456-21     :2555
FrontLeft       :GHI-789FL-21   :136
FrontRight      :GHI-789FR-21   :136
BackLeft        :GHI-789BL-21   :152
BackRight       :GHI-789BR-21   :152
Total Price = 4132

Summary

  • Use the Visitor pattern to define an operation to be performed on the elements/parts of a hierarchical object structure.
  • Use the Visitor pattern to define the new operation without needing to change the classes of the elements/parts on that it operates.
  • When designing your application, you can provision for the future possibility of needing to run custom operations on objects, by implementing the Visitor interface in anticipation.
  • Usage of the Visitor pattern helps to ensure that your classes conform to the single responsibility principle due to them implementing the custom visitor behavior in a separate class.