Singleton ensures a class only has one instance, and provide a global point of access to it.

How It Works

  1. Create only one instance of the Singleton class; and
  2. If an instance exists, then serve the same object again.

The main features of Singleton are:

  • Ensuring that one and only one object of the class gets created;

  • Providing an access point for an object that is global to the program; and

  • Controlling concurrent access to resources that are shared.

When to Use It

  • In situations that require exactly one instance of a class, that must be accessible to clients from a well-known access point. See the Counter example.

Caution: Singletons can be a problem in multi-threaded applications, especially when they manipulate mutable data.

Tip: Singletons work well for immutable data, such as reading from some data source, since anything that can’t change isn’t going to run into thread clash problems.

Example: Counter

In this example we implement a Counter that inherits the qualities of Singleton.

Counter <- R6::R6Class(inherit = Singleton, public = list(
    count = 0,
    add_1 = function(){self$count = self$count + 1; invisible(self)}
))

Whenever we call the constructor on Counter, we always get the exact same instance:

counter_A <- Counter$new()
counter_B <- Counter$new()

identical(counter_A, counter_B, ignore.environment = FALSE)
#> [1] TRUE

The two objects are equal and located at the same address; thus, they are the same object.

When we make a change in any of the class instances, the rest of the instances are changed as well.

# How many times has the counter been increased?
counter_A$count
#> [1] 0

# Increase the counter by 1
counter_A$add_1()

# How many times have the counters been increased?
counter_A$count
#> [1] 1
counter_B$count 
#> [1] 1

Example: Data Transfer Object (DTO)

In this example, we crate access to a database that only establishes one connection per session.

Notice how the initialize public method first calls the initialize of the Singleton, and then appends its own initialisation routine.

DTO <- R6::R6Class(classname = "DTO", inherit = Singleton, public = list(
    con = NULL,
    initialize = function(){
        super$initialize()
        self$establish_connection()
    },
    establish_connection = function(){
        self$con <- DBI::dbConnect(RSQLite::SQLite(), dbname = ":memory:")
        return(self)
    },
    dbSendQuery = function(statement){
        res <- DBI::dbSendQuery(self$con, statement)
        return(res)
    }
))

Once a database connection has established during the session, other calls to DTO instantiate objects that use the same database connection.

database_A <- DTO$new()
database_B <- DTO$new()
identical(database_A, database_B)
#> [1] TRUE

Further Reading

Singleton on Wikipedia

Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides. 1995. Design patterns: elements of reusable object-oriented software. Pearson Education India.