#' R6 parent class
<- R6::R6Class(
parent_class "parent_class",
public = list(
#' @field name Class label
name = "",
#' @description
#' Initialize the class
initialize = function(name) {
$name = name
selfinvisible(self)
},
#' @description
#' Update accumulator by value
update = function(n = 1) {
$accumulator <- private$accumulator + n
private
},
#' @description
#' Return the value of the accumulator
count = function() {
return(private$accumulator)
},
#' @description
#' Add a new step
#' @param step type of step to add
add = function(step) {
<- paste0(sample(LETTERS, size = 8), collapse = "")
new_name <- get(step)$new(name = new_name)
new_step $steps[[new_name]] <- new_step
private
},
#' @description
#' Run the steps
run = function() {
for (s in private$steps) {
$execute(parent = self)
s
}
},
#' @description
#' Return status of steps
status = function() {
lapply(private$steps, function(s) {
list(name = s$name, value = s$val, status = s$status)
|> dplyr::bind_rows()
})
}
),
private = list(
accumulator = 0,
steps = list()
) )
R has several object-oriented systems and I’m a big fan of R6. Detailed below is a specific use-case. I wanted a parent class that held a list of child classes with thet specification that the child class instances could update the parent class instance.
Parent Class
The parent class is shown below along with a table detailing the public and private fields and methods. the purpose of the parent class is to hold a series of steps along with methods to interact with them. In addition, the parent class has a private field called accumulator
which we will update from the child classes.
public/private | field/method | description |
---|---|---|
public | name | a label |
public | initialize() | create a new instance |
public | update(n) | update the accumulator by n (default n = 1) |
public | count() | return the value of the accumulator |
public | add(step) | add a new step to the parent class (steps are child classes) |
public | run() | execute all the steps (child classes) |
public | status() | return the status of each step |
public | accumulator | an accumulator, intially set to 0 |
private | steps | list of steps |
Child Class - Generic
For child classes we first build a generic class that can manage any function that is common across the child classes. We can then use the property of inheritance so that the generic child class methods are available for all child classes, adding any specific methods. The generic class is shown below along with a list of public fields and methods.
field/method | description |
---|---|
name | a label |
val | numeric to store a class value (intial = NA) |
status | status notification - possible values are initialized and run |
initialize() | create a new instance |
execute() | execute the class - set val equal to parent$count() and change status to run |
<- R6::R6Class(
child_class "child_class",
public = list(
#' @field name class label
name = NULL,
#' @field val class value
val = NA,
#' @field status class status
status = "initialized",
#' @description
#' Initialize class
initialize = function(name) {
$name <- name
self
},
#' @description
#' Execute the class. Set internal value equal to the
#' parent class `accumulator`
#' @param parent Parent class
execute = function(parent) {
$val <- parent$count()
self$status <- "run"
self
}
) )
Child Class - Child Classes
We define two child classes. The first increases the parent accumulator field by one, and the second doubles it. Each child class inherits the generic class to avoid repetition. The only change from the generic class is the public execute()
method.
field/method | description |
---|---|
execute() | execute the class - set val equal to parent$count(), change parent accumulator according to the step, and change status to run |
<- R6::R6Class(
step_add_one "step_add_one",
inherit = child_class,
public = list(
#' @description
#' Execute the class. Set internal value equal to the
#' parent class `accumulator` and increase the parent
#' class `accumulator` by 1.
#' @param parent Parent class
execute = function(parent) {
$val <- parent$count()
self$update()
parent$status <- "run"
self
}
) )
<- R6::R6Class(
step_double "step_double",
inherit = child_class,
public = list(
#' @description
#' Execute the class. Set internal value equal to the
#' parent class `accumulator` and multiply the parent
#' class `accumulator` by 2.
#' @param parent Parent class
execute = function(parent) {
$val <- parent$count()
self$update(n = parent$count())
parent$status <- "run"
self
}
) )
Execution
# initialize the parent class
<- parent_class$new('parent class')
my_parent
# step_add_one - add a single number to the counter
$add('step_add_one')
my_parent$add('step_add_one')
my_parent
# step_double - double the counter
$add('step_double')
my_parent$add('step_double')
my_parent
# return the counter value
$count() my_parent
[1] 0
# print the status of each step
$status() my_parent
# A tibble: 4 × 3
name value status
<chr> <lgl> <chr>
1 UWDIGVYP NA initialized
2 WXYRAUTM NA initialized
3 PKBNVDQW NA initialized
4 QLAVHOKG NA initialized
# run - execute each step in turn
$run()
my_parent
# return the counter value
$count() my_parent
[1] 8
# print the status of each step
$status() my_parent
# A tibble: 4 × 3
name value status
<chr> <dbl> <chr>
1 UWDIGVYP 0 run
2 WXYRAUTM 1 run
3 PKBNVDQW 2 run
4 QLAVHOKG 4 run
Running the code above creates a parent class instance called my_parent
. Four steps are added to the parent class (step_add_one
twice and step_double
twice). At this point, the accumulator (my_parent$count()
) is 0 and my_parent$status()
shows all steps are initialized
as no steps have been executed. After my_parent$run()
is run and all steps executed, the accumulator is 8 (add 1, add 1, double, double) and my_parent$status()
shows all steps are run
.
The accumulator is a field in the parent and it is updated through the child classes.