Skip to content

Singleton Design Pattern

Video Lecture

Section Video Links
Singleton Pattern Singleton Singleton Pattern 
Singleton Use Case Singleton Use Case Singleton Use Case 

Overview

Sometimes you need an object in an application where there is only one instance.

You don't want there to be many versions, for example, you have a game with a score, and you want to adjust it. You may have accidentally created several instances of the class holding the score object. Or, you may be opening a database connection, there is no need to create many, when you can use the existing one that is already in memory. You may want a logging component, and you want to ensure all classes use the same instance. So, every class could declare their own logger component, but behind the scenes, they all point to the same memory address.

By creating a class and following the Singleton pattern, you can enforce that even if any number of instances were created, they will still refer to the original class.

The Singleton can be accessible globally, but it is not a global variable. The Singleton class can be instanced at any time, but after it is first instanced, any new instances will point to the same instance as the first.

Singleton UML Diagram

Singleton UML Diagram

Source Code

In the source code below, when the Singleton constructor is called, it checks if the static instance property has already been set. If so, then instead of creating a new instance of a Singleton, it will return the reference to the first Singleton created.

See how when I create the two Objects (Singletons), I am also passing in a number to the constructor that I can use to act as some kind of ID. At the end of the code, I log the IDs for both Singleton objects, and they both return the number 1. This indicates that OBJECT2 is really just OBJECT1 behind the scenes.

A Singleton is not the same as a class containing all static properties and methods, but you could create a class of static properties and methods instead if it achieved the same purpose that you needed of controlling access to a single resource. The real difference here is in the constructor of this Singleton example in how it returns a reference to the original instance instead of creating a second, or third instance. The reason for using a Singleton would be that your Singleton can implement an interface or derive from a base class, and also the Singleton isn't actually instantiated in memory until the first time it is created when using the new keyword.

./src/singleton/singleton-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
// Singleton Concept Sample Code

export class Singleton {
    // The Singleton Class
    static instance: Singleton
    id: number

    constructor(id: number) {
        this.id = id
        if (Singleton.instance) {
            return Singleton.instance
        }
        Singleton.instance = this
    }
}

// The Client
// All uses of the singleton point to the same original object

const OBJECT1 = new Singleton(1) // setting its id property to 1
const OBJECT2 = new Singleton(2) // setting its id property to 2

console.log(OBJECT1 === OBJECT2) // = true
console.log(OBJECT1.id) // returns 1
console.log(OBJECT2.id) // returns 1

Output

node ./dist/singleton/singleton-concept.js
true

SBCODE Editor

<>

Singleton Use Case

In the example, there are three games created. They are all independent instances created from their own class, but they all share the same leaderboard. The leaderboard is a Singleton.

It doesn't matter how the Games where created, or how they reference the leaderboard, it is always a Singleton.

Each game independently adds a winner, and all games can read the altered leaderboard regardless of which game updated it.

Example UML Diagram

Singleton Use Case Diagram

Source Code

./src/singleton/client.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// Singleton Use Case Example Code

import { Game1 } from './game1'
import { Game2 } from './game2'
import { Game3 } from './game3'

// The Client
// Despite all games instantiating a leaderboard, they all point
// to the same memory object since the leaderboard is a singleton.
const GAME1 = new Game1()
GAME1.addWinner(2, 'Cosmo')

const GAME2 = new Game2()
GAME2.addWinner(3, 'Sean')

const GAME3 = new Game3()
GAME3.addWinner(1, 'Emmy')

GAME1.leaderboard.print()
GAME2.leaderboard.print()
GAME3.leaderboard.print()

./src/singleton/game1.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// A Game Class that uses the Leaderboard Singleton

import Leaderboard from './leaderboard'
import Game from './igame'

export class Game1 implements Game {
    leaderboard: Leaderboard

    constructor() {
        this.leaderboard = new Leaderboard()
    }

    addWinner(position: number, name: string): void {
        this.leaderboard.addWinner(position, name)
    }
}

./src/singleton/game2.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// A Game Class that uses the Leaderboard Singleton

import Leaderboard from './leaderboard'
import Game from './igame'

export class Game2 implements Game {
    leaderboard: Leaderboard

    constructor() {
        this.leaderboard = new Leaderboard()
    }

    addWinner(position: number, name: string): void {
        this.leaderboard.addWinner(position, name)
    }
}

./src/singleton/game3.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// A Game Class that uses the Leaderboard Singleton

import Leaderboard from './leaderboard'
import Game from './igame'

export class Game3 implements Game {
    leaderboard: Leaderboard

    constructor() {
        this.leaderboard = new Leaderboard()
    }

    addWinner(position: number, name: string): void {
        this.leaderboard.addWinner(position, name)
    }
}

./src/singleton/leaderboard.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
// A Leaderboard Singleton Class

export default class Leaderboard {
    static instance: Leaderboard
    #table: { [id: number]: string } = {}

    constructor() {
        if (Leaderboard.instance) {
            return Leaderboard.instance
        }
        Leaderboard.instance = this
    }

    public addWinner(position: number, name: string): void {
        this.#table[position] = name
    }

    public print(): void {
        console.log('-----------Leaderboard-----------')
        for (const key in this.#table) {
            console.log(`|\t${key}\t|\t${this.#table[key]}\t|`)
        }
        console.log()
    }
}

./src/singleton/igame.ts

1
2
3
4
5
// A Game Interface

export default interface IGame {
    addWinner(position: number, name: string): void
}

Output

node ./dist/singleton/client
-----------Leaderboard-----------
|       1       |       Emmy    |
|       2       |       Cosmo   |
|       3       |       Sean    |

-----------Leaderboard-----------
|       1       |       Emmy    |
|       2       |       Cosmo   |
|       3       |       Sean    |

-----------Leaderboard-----------
|       1       |       Emmy    |
|       2       |       Cosmo   |
|       3       |       Sean    |

SBCODE Editor

<>

Summary

  • To be a Singleton, there must only be one copy of the Singleton, no matter how many times, or in which class it was instantiated.
  • You want the attributes or methods to be globally accessible across your application, so that other classes may be able to use the Singleton.
  • You can use Singletons in other classes, as I did with the leaderboard, and they will all use the same Singleton instance regardless.
  • You want controlled access to a sole instance.
  • A singleton differs from a class containing just static methods and properties in the way that you can make your Singleton implement an interface and/or extend a base class. You also create an instance of a Singleton at runtime using the new keyword.