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 <- e1
e1$c <- 4
e2$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 + x
invisible(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 <- name
self$age <- age
}
))
hadley <- Person$new("Hadley", age = "thirty-eight")
#> Error in initialize(...): is.numeric(age) is not TRUE
hadley <- 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 <- name
self$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: NA
hadley
#> <Person>
#> Public:
#> age: 38
#> clone: function (deep = FALSE)
#> initialize: function (name, age = NA)
#> name: Hadley
hadley$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 + x
invisible(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 <- 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
#> 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.0808
x$random
#> [1] 0.834
x$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 <- 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 only