R6
R6 has two special properties:
- It uses the encapsulated OOP paradigm, which means that methods belong to objects, not generics, and you call them like object$method().
- R6 objects are mutable, which means that they are modified in place, and hence have reference semantics.(具有引用语义)
Prerequisites
环境总是在原地修改。此属性有时被描述为引用语义,因为当您修改环境时,与该环境的所有现有绑定继续具有相同的引用。e1 <- rlang::env(a = 1, b = 2, c = 3)e2 <- e1e1$c <- 4e2$c#> [1] 4
Classes and methods
R6创建的唯一方法:R6Class():
- 第一个参数是classname,表示创建的class的名字,通常首字母大写
- 第二个参数public提供构成对象公共接口的方法(函数)和字段(其他)的列表。通常小写
通过调用new()方法从类中构造一个新对象。在 R6 中,方法属于对象,因此您可以使用$来访问new():Accumulator <- R6Class("Accumulator", list(sum = 0,add = function(x = 1) {self$sum <- self$sum + xinvisible(self)}))
访问字段x <- Accumulator$new()
x$add(4)x$sum#> [1] 4
Method chaining
R6 方法应始终self不可见地返回。这将返回“当前”对象,并可以将多个方法调用链接在一起
如果没有invisible(self), 那将无法多个方法链接调用x$add(10)$add(10)$sum#> [1] 24
Important methods
应该为大多数类定义两个重要的方法:$initialize()和$print(). 它们不是必需的,但提供它们将使您的课程更易于使用。
$initialize() overrides the default behaviour of $new().以下代码确定输入的name是字符串,age是数值,并且只有唯一输入值
Person <- R6Class("Person", list(name = NULL,age = NA,initialize = function(name, age = NA) {stopifnot(is.character(name), length(name) == 1)stopifnot(is.numeric(age), length(age) == 1)self$name <- nameself$age <- age}))hadley <- Person$new("Hadley", age = "thirty-eight")#> Error in initialize(...): is.numeric(age) is not TRUEhadley <- Person$new("Hadley", age = 38)
同理。定义$print()允许您覆盖默认打印行为。与任何因其副作用而调用的 R6 方法一样,$print()应该返回invisible(self).
Person <- R6Class("Person", list(name = NULL,age = NA,initialize = function(name, age = NA) {self$name <- nameself$age <- age},print = function(...) {cat("Person: \n")cat(" Name: ", self$name, "\n", sep = "")cat(" Age: ", self$age, "\n", sep = "")invisible(self)}))hadley2 <- Person$new("Hadley")hadley2#> Person:#> Name: Hadley#> Age: NAhadley#> <Person>#> Public:#> age: 38#> clone: function (deep = FALSE)#> initialize: function (name, age = NA)#> name: Hadleyhadley$print#> NULL
上述代码说明了 R6 的一个重要方面。因为方法绑定到单个对象,先前创建的hadley对象没有得到这个新方法:
Adding methods after creation
在已有的class的基础上再添加新的方法,. Add new elements to an existing class with $set()
Accumulator <- R6Class("Accumulator")Accumulator$set("public", "sum", 0)Accumulator$set("public", "add", function(x = 1) {self$sum <- self$sum + xinvisible(self)})
Inheritance
创建class元素的时候,提供了inherit argument
AccumulatorChatty <- R6Class("AccumulatorChatty",inherit = Accumulator,public = list(add = function(x = 1) {cat("Adding ", x, "\n", sep = "")super$add(x = x)}))x2 <- AccumulatorChatty$new()x2$add(10)$add(1)$sum#> Adding 10#> Adding 1#> [1] 11
add()会覆盖之前父类的add方法,但是 通过使用$super,就可以使用父类的方法
Introspection
class(hadley2)#> [1] "Person" "R6"
每个 R6 对象都有一个反映其 R6 类层次结构的 S3 类。这意味着确定类(及其继承的所有类)的最简单方法是使用class():
Controlling access
R6Class()还有两个与 类似的参数public:
- private允许您创建仅在类内部而不是外部可用的字段和方法。
- active允许您使用访问器函数来定义动态或活动字段。
private
Person <- R6Class("Person",public = list(initialize = function(name, age = NA) {private$name <- nameprivate$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: NAhadley3$name#> NULL
active field
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.0808x$random#> [1] 0.834x$random#> [1] 0.601
活动字段和私有字段的结合
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 <- valueself}}),public = list(initialize = function(name, age = NA) {private$.name <- nameprivate$.age <- age}))hadley4 <- Person$new("Hadley", age = 38)hadley4$name#> [1] "Hadley"hadley4$name <- 10#> Error in (function (value) : is.character(value) is not TRUEhadley4$age <- 20#> Error: `$age` is read only
