14.3 Controlling access
R6Class() has two other arguments that work similarly to public:
privateallows you to create fields and methods that are only available from within the class, not outside of it.activeallows you to use accessor functions to define dynamic, or active, fields.
These are described in the following sections.
14.3.1 Privacy
With R6 you can define private fields and methods, elements that can only be accessed from within the class, not from the outside53. There are two things that you need to know to take advantage of private elements:
The
privateargument toR6Classworks in the same way as thepublicargument: you give it a named list of methods (functions) and fields (everything else).Fields and methods defined in
privateare available within the methods usingprivate$instead ofself$. You cannot access private fields or methods outside of the class.
To make this concrete, we could make $age and $name fields of the Person class private. With this definition of Person we can only set $age and $name during object creation, and we cannot access their values from outside of the class.
Person <- R6Class("Person",
public = list(
initialize = function(name, age = NA) {
private$name <- name
private$age <- age
},
print = function(...) {
cat("Person: \n")
cat(" Name: ", private$name, "\n", sep = "")
cat(" Age: ", private$age, "\n", sep = "")
}
),
private = list(
age = NA,
name = NULL
)
)
hadley3 <- Person$new("Hadley")
hadley3
#> Person:
#> Name: Hadley
#> Age: NA
hadley3$name
#> NULLThe distinction between public and private fields is important when you create complex networks of classes, and you want to make it as clear as possible what it’s ok for others to access. Anything that’s private can be more easily refactored because you know others aren’t relying on it. Private methods tend to be less important in R compared to other programming languages because the object hierarchies in R tend to be simpler.
14.3.2 Active fields
Active fields allow you to define components that look like fields from the outside, but are defined with functions, like methods. Active fields are implemented using active bindings (Section 7.2.6). Each active binding is a function that takes a single argument: value. If the argument is missing(), the value is being retrieved; otherwise it’s being modified.
For example, you could make an active field random that returns a different value every time you access it:
Rando <- R6::R6Class("Rando", active = list(
random = function(value) {
if (missing(value)) {
runif(1)
} else {
stop("Can't set `$random`", call. = FALSE)
}
}
))
x <- Rando$new()
x$random
#> [1] 0.0808
x$random
#> [1] 0.834
x$random
#> [1] 0.601
Active fields are particularly useful in conjunction with private fields, because they make it possible to implement components that look like fields from the outside but provide additional checks. For example, we can use them to make a read-only age field, and to ensure that name is a length 1 character vector.
Person <- R6Class("Person",
private = list(
.age = NA,
.name = NULL
),
active = list(
age = function(value) {
if (missing(value)) {
private$.age
} else {
stop("`$age` is read only", call. = FALSE)
}
},
name = function(value) {
if (missing(value)) {
private$.name
} else {
stopifnot(is.character(value), length(value) == 1)
private$.name <- value
self
}
}
),
public = list(
initialize = function(name, age = NA) {
private$.name <- name
private$.age <- age
}
)
)
hadley4 <- Person$new("Hadley", age = 38)
hadley4$name
#> [1] "Hadley"
hadley4$name <- 10
#> Error in (function (value) : is.character(value) is not TRUE
hadley4$age <- 20
#> Error: `$age` is read only14.3.3 Exercises
Create a bank account class that prevents you from directly setting the account balance, but you can still withdraw from and deposit to. Throw an error if you attempt to go into overdraft.
Create a class with a write-only
$passwordfield. It should have$check_password(password)method that returnsTRUEorFALSE, but there should be no way to view the complete password.Extend the
Randoclass with another active binding that allows you to access the previous random value. Ensure that active binding is the only way to access the value.Can subclasses access private fields/methods from their parent? Perform an experiment to find out.
Because R is such a flexible language, it’s technically still possible to access private values, but you’ll have to try much harder, spelunking in to the details of R6’s implementation.↩︎