R 面向对象编程(四)—— R6
R6
4.1 介绍
R6
是 R
的封装式面向对象编程的实现,比内置的 RC
类更简单,更快,更轻量级。
与内置的 R3
、R4
和 RC
不同,R6
是一个单独的 R
包,因此不需要依赖 methods
包。
R6
类支持:
- 属性和方法的公有化和私有化
- 主动绑定
- 跨包之间的继承
为什么这个包叫 R6
呢?
哈哈,当然是为了保持队形了啊
S3、S4、S5、S6
,虽然 RC
的官方名称并不是 S5
,但不妨碍大家这么称呼。
学过其他语言的面向对象编程系统的应该知道,我们前面几节讲的 R
中几种系统设计的并不够好,所以,需要 R6
这样的包。
4.2 创建 R6 对象
R6
是第三方包,所以记得先安装一下
install.packages("R6")
library(R6)
R6
是通过 R6Class()
函数创建类
R6Class(classname = NULL, public = list(), private = NULL,
active = NULL, inherit = NULL, lock_objects = TRUE, class = TRUE,
portable = TRUE, lock_class = FALSE, cloneable = TRUE,
parent_env = parent.frame(), lock)
参数列表
创建一个简单的 Person
类
Person <- R6Class(
"Person",
public = list(
name = NA,
initialize = function(name) {
self$name <- name
},
say = function() {
cat("my name is ", self$name)
}
)
)
创建实例,同样使用 $new
方法来实例化
> tom <- Person$new(name = "tom")
> tom
<Person>
Public:
clone: function (deep = FALSE)
initialize: function (name)
name: tom
say: function ()
查看类与实例的类型
> class(Person)
[1] "R6ClassGenerator"
> class(tom)
[1] "Person" "R6"
> otype(tom)
[1] "S3"
> otype(Person)
[1] "S3"
我们可以看到,其实 R6
系统是基于 S3
构建的,这也是它不同于 RC
的原因
4.3 公有成员与私有成员
在 R6
系统的类定义中,可以设置公有成员和私有成员。这一特征与 Java
和 C++
的类很像,使用私有成员来隐藏一些数据属性和方法。
在 R6
中公有成员的访问使用的是 self
对象来引用,而私有需要用 private
对象来引用。
在前面的例子中,我们使用的是 self$name
来获取公有属性 name
,现在让我们来添加私有成员
Person <- R6Class(
"Person",
public = list(
name = NA,
initialize = function(name, money) {
self$name <- name
private$money <- money
},
say = function() {
cat("my name is ", self$name)
},
incSalary = function(percent) {
private$setMoney(private$money * (1 + percent))
invisible(self)
}
),
private = list(
money = NA,
setMoney = function(m) {
cat(paste0("change ", self$name, "'s salary!\n"))
private$money <- m
}
)
)
我们添加了私有属性 money
和私有函数 setMoney
我们先创建一个实例化对象
> tom <- Person$new(name = "tom", 1000)
> tom
<Person>
Public:
clone: function (deep = FALSE)
incSalary: function (percent)
initialize: function (name, money)
name: tom
say: function ()
Private:
money: 1000
setMoney: function (m)
然后调用对应的方法
> tom$name
[1] "tom"
> tom$money
NULL
> tom$incSalary(0.1)
change tom's salary!
> tom$setMoney
NULL
> tom$setMoney()
错误: 不适用于非函数
我们可以使用 $
符号正常访问公有成员,但是无法访问私有成员
注意:我们在 incSalary
函数中添加了一行 invisible(self)
,这样我们就可以对这个方法进行链式调用了,例如
> tom$incSalary(0.1)$incSalary(0.2)$incSalary(0.3)
change tom's salary!
change tom's salary!
change tom's salary!
> tom
<Person>
Public:
clone: function (deep = FALSE)
incSalary: function (percent)
initialize: function (name, money)
name: tom
say: function ()
test: function ()
Private:
money: 1887.6
setMoney: function (m)
注意:我们在访问成员时都是使用了 self
或 private
对象,而不管是在 public
参数里面还是 private
参数里面
我们可以测试一下 self
和 private
到底是什么,我们在上面的例子中,添加一个 test
公有函数
Person <- R6Class(
"Person",
public = list(
name = NA,
initialize = function(name, money) {
self$name <- name
private$money <- money
},
say = function() {
cat("my name is ", self$name)
},
incSalary = function(percent) {
private$setMoney(private$money * (1 + percent))
},
test = function() {
print(self)
print(strrep("=", 20))
print(private)
print(strrep("=", 20))
print(ls(envir = private))
}
),
private = list(
money = NA,
setMoney = function(m) {
cat(paste0("change ", self$name, "'s salary!"))
private$money <- m
}
)
)
测试一下
> tom <- Person$new(name = "tom", 1000)
> tom$test()
<Person>
Public:
clone: function (deep = FALSE)
incSalary: function (percent)
initialize: function (name, money)
name: tom
say: function ()
test: function ()
Private:
money: 1000
setMoney: function (m)
[1] "===================="
<environment: 0x7fe1d2135cf0>
[1] "===================="
[1] "money" "setMoney"
从上面的输出结果可以看出,self
对象更像是实例化的对象本身,而 private
则是一个环境空间。这个环境空间就像是变量的作用域,因此,private
只在类中被调用,而对于类外部是不可见的。
4.4 主动绑定
主动绑定可以让对函数调用看起来像是在访问属性,主动绑定总是公开成员,外部可见的。
这与 Python
中的 @property
装饰器是一样的,有些时候,我们并不想直接把数据属性暴露在外面,被随意修改。
例如,我们有一个 Student
类,包含一个 score
属性,但是不想将其暴露在外面被随意修改,所以我们将其设置为私有属性,同时定义 get/set
方法,并在 set
方法中控制有效范围
Student <- R6Class(
"Student",
public = list(
name = NA,
initialize = function(name) {
self$name <- name
},
getScore = function() {
return(private$score)
},
setScore = function(score) {
if (score < 0 || score > 100)
stop("Score incorrect!")
private$score <- score
}
),
private = list(
score = NA
)
)
使用
> sam <- Student$new("sam")
> sam$setScore(99)
> sam$getScore()
[1] 99
> sam$setScore(101)
Error in sam$setScore(101) : Score incorrect!
这样是可以达到我们的目的,但还是不能像属性那样用起来方便
所以 R6
为我们提供了 active
参数,重新改写上面的例子
Student <- R6Class(
"Student",
public = list(
name = NA,
initialize = function(name) {
self$name <- name
}
),
private = list(
.score = NA
),
active = list(
score = function(s) {
if (missing(s))
return(private$.score)
if (s < 0 || s > 100)
stop("Score incorrect!")
private$.score <- s
}
)
)
注意:public
, private
和 active
参数内的属性名必须唯一,所以我们将私有属性改为了 .score
> sam <- Student$new("sam")
> sam
<Student>
Public:
clone: function (deep = FALSE)
initialize: function (name)
name: sam
score: active binding
Private:
.score: NA
> sam$score
[1] NA
> sam$score <- 100
> sam$score
[1] 100
4.5 继承
R6 通过 inherit
参数指定父类,例如,我们定义一个 worker
类,它继承自上面的 Person
类
Worker <- R6Class(
"Worker",
inherit = Person,
public = list(
company = "Gene",
info = function() {
print("NGS analysis!")
}
)
)
创建对象并使用父类的方法
> siri <- Worker$new("Siri", 100)
> siri$incSalary(0.1)
change Siri's salary!
> siri
<Worker>
Inherits from: <Person>
Public:
clone: function (deep = FALSE)
company: Gene
incSalary: function (percent)
info: function ()
initialize: function (name, money)
name: Siri
say: function ()
test: function ()
Private:
money: 110
setMoney: function (m)
我们可以使用 super
对象来调用父类的方法,让我们来重写 incSalary
方法
Worker <- R6Class(
"Worker",
inherit = Person,
public = list(
company = "Gene",
info = function() {
print("NGS analysis!")
},
incSalary = function(percent) {
super$incSalary(percent + 0.1)
}
)
)
运行与上面相同的代码
> siri <- Worker$new("Siri", 100)
> siri$incSalary(0.1)
change Siri's salary!
> siri
<Worker>
Inherits from: <Person>
Public:
clone: function (deep = FALSE)
company: Gene
incSalary: function (percent)
info: function ()
initialize: function (name, money)
name: Siri
say: function ()
test: function ()
Private:
money: 120
setMoney: function (m)
可以看到,工资的增长增加了 0.1
4.6 引用对象字段
如果您的 R6 类的属性中包含其他类的实例化对象时,该对象将在 R6
对象的所有实例中共享。例如
ShareClass <- R6Class(
"ShareClass",
public = list(
num = NULL
)
)
Common <- R6Class(
"Common",
public = list(
share = ShareClass$new()
)
)
> c1 <- Common$new()
> c1$share$num <- 1
> c2 <- Common$new()
> c2$share$num <- 2
> c1$share$num
[1] 2
注意:不能把实例化对象放在 initialize
方法中
UnCommon <- R6Class(
"Common",
public = list(
share = NULL,
initialize = function() {
share <<- ShareClass$new()
}
)
)
n1 <- UnCommon$new()
n1$share$num <- 1
n2 <- UnCommon$new()
n2$share$num <- 2
n1$share$num
可以看到,share
属性并没有改变
4.7 可移植和不可移植类
portable
参数可以设置 R6
类是否为可移植类型还是不可移植类型,主要区别在于:
- 可移植类支持跨包继承,但是不可移植类型的兼容性不好
- 可移植类使用
self
和private
来访问成员。不可移植类直接使用属性名称来访问,如share
,并使用<<-
操作符对这些成员进行赋值
4.8 为现有类添加成员
有时候,我们需要对已经创建的类添加新的成员,可以使用 $set()
方法来完成。
例如,我们为 Student
类添加一个属性
> Student$set("public", "age", 21)
> sam <- Student$new("sam")
> sam
<Student>
Public:
age: 21
clone: function (deep = FALSE)
initialize: function (name)
name: sam
score: active binding
Private:
.score: NA
当然也可以使用这种方式修改属性值
> Student$set("public", "age", 18, overwrite = TRUE)
> sam <- Student$new("sam")
> sam
<Student>
Public:
age: 18
clone: function (deep = FALSE)
initialize: function (name)
name: sam
score: active binding
Private:
.score: NA
注意:我们设置了 overwrite=TRUE
添加一个方法
> Student$set("public", "getName", function() self$name)
> sam <- Student$new("sam")
> sam$getName()
[1] "sam"
4.9 打印对象
R6
对象有一个默认的 print
方法,列出对象的所有成员。我们可以为类自定义一个 print
方法,那么它将覆盖默认的方法
Student <- R6Class(
"Student",
public = list(
name = NA,
initialize = function(name) {
self$name <- name
},
print = function(...) {
cat("class", class(self), "\n")
cat(ls(self), sep = ',')
}
),
private = list(
.score = NA
),
active = list(
score = function(s) {
if (missing(s))
return(private$.score)
if (s < 0 || s > 100)
stop("Score incorrect!")
private$.score <- s
}
)
)
> sam <- Student$new("sam")
> print(sam)
class Student R6
clone,initialize,name,print,score