Singleton ensures a class only has one instance, and provide a global point of access to it.
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.
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.
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] TRUEThe 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] 1In 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