Skip to content

Strategy Design Pattern

Video Lecture

Section Video Links
Strategy Pattern Strategy Strategy Pattern  
Strategy Use Case Strategy Use Case Strategy Use Case  

Overview

The Strategy Pattern is similar to the State Pattern, except that the client passes in the algorithm that the context should run.

The algorithm should be contained within a class that implements the particular strategies interface.

An application that sorts data is a good example of where you can incorporate the Strategy pattern.

There are many methods of sorting a set of data. E.g., Quicksort, Mergesort, Introsort, Heapsort, Bubblesort. See https://en.wikipedia.org/wiki/Sorting_algorithm for more examples.

The user interface of the client application can provide a drop-down menu to allow the user to try the different sorting algorithms.

Upon user selection, a reference to the algorithm will be passed to the context and processed using this new algorithm instead.

The Strategy and State appear very similar, a good way to differentiate them is to consider whether the state of the context is choosing the algorithm at runtime, or whether the algorithm is being passed into it.

Software Plugins can be implemented using the Strategy pattern.

Terminology

  • Strategy Interface: An interface that all Strategy subclasses/algorithms must implement.
  • Concrete Strategy: The subclass that implements an alternative algorithm.
  • Context: This is the object that receives the concrete strategy in order to execute it.

Strategy UML Diagram

Strategy UML Diagram

Source Code

There is a Context and three different strategies to choose from.

Each Strategy is executed in turn by the context.

./src/strategy/strategy-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
// The Strategy Pattern Concept

class ObjectContext {
    // This is the object whose behavior will change

    request(strategy: IStrategyConstructor) {
        // The request is handled by the class passed in
        return new strategy()
    }
}

interface IStrategyConstructor {
    // A Constructor for the IStrategy
    new (): IStrategy
}

interface IStrategy {
    // A strategy Interface
    method(): string
}

class ConcreteStrategyA implements IStrategy {
    // A Concrete Strategy Subclass

    method() {
        return 'I am ConcreteStrategyA'
    }
}

class ConcreteStrategyB implements IStrategy {
    // A Concrete Strategy Subclass

    method() {
        return 'I am ConcreteStrategyB'
    }
}

class ConcreteStrategyC implements IStrategy {
    // A Concrete Strategy Subclass

    method() {
        return 'I am ConcreteStrategyC'
    }
}

// The Client
const OBJECT_CONTEXT = new ObjectContext()

console.log(OBJECT_CONTEXT.request(ConcreteStrategyA).method())
console.log(OBJECT_CONTEXT.request(ConcreteStrategyB).method())
console.log(OBJECT_CONTEXT.request(ConcreteStrategyC).method())

Output

node ./dist/strategy/strategy-concept.js
I am ConcreteStrategyA
I am ConcreteStrategyB
I am ConcreteStrategyC

Strategy Use Case

A game character is moving through an environment. Depending on the situation within the current environment, the user decides to use a different movement algorithm. From the perspective of the object/context, it is still a move, but the implementation is encapsulated in the subclass at the handle.

In a real game, the types of things that a particular move could affect is which animation is looped, the audio, the speed, the camera follow mode and more.

Strategy Example Use Case UML Diagram

Strategy Example Use Case UML Diagram

Source Code

./src/strategy/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
// The Strategy Pattern Example Use Case

class GameCharacter {
    // This is the context whose strategy will change

    #position: [number, number] = [0, 0]

    move(movementStyle: IMoveConstructor) {
        // The movement algorithm has been decided by the client
        new movementStyle().move(this.#position)
    }
}

interface IMoveConstructor {
    // A Constructor for the IMove
    new (): IMove
}

interface IMove {
    // The Move Strategy Interface
    move(position: [number, number]): void
}

class Walking implements IMove {
    // A concrete movement strategy for walking

    move(position: [number, number]) {
        position[0] += 1
        console.log(`I am Walking. New position = ${position}`)
    }
}

class Sprinting implements IMove {
    // A concrete movement strategy for sprinting

    move(position: [number, number]) {
        position[0] += 2
        console.log(`I am Running. New position = ${position}`)
    }
}

class Crawling implements IMove {
    // A concrete movement strategy for crawling

    move(position: [number, number]) {
        position[0] += 0.5
        console.log(`I am Crawling. New position = ${position} `)
    }
}

// The Client
const GAME_CHARACTER = new GameCharacter()

GAME_CHARACTER.move(Walking)
// Character sees the enemy
GAME_CHARACTER.move(Sprinting)
// Character finds a small cave to hide in
GAME_CHARACTER.move(Crawling)

Output

node ./dist/strategy/client.js
I am Walking. New position = 1,0
I am Running. New position = 3,0
I am Crawling. New position = 3.5,0

Summary

  • While the Strategy pattern looks very similar to the State pattern, the assigned strategy subclass/algorithm is not changing any state of the context that would affect which algorithm is used.

  • The Strategy pattern is about having a choice of implementations that accomplish the same relative task.

  • The particular strategies' algorithm is encapsulated in order to keep the implementation de coupled from the context.

  • Software Plugins can be implemented using the Strategy pattern.