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] 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
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