第 5 章 基于RC的面向对象编程
RC(Reference Class)对象系统从底层上改变了原有S3和S4对象系统的设计,去掉了泛型函数,其真正的以类为基础实现面向对象的特征。在R中常备简记为R5http://adv-r.had.co.nz/R5.html(但这不是官方的名称),而是为了从形式上和S3和S4相匹配。
5.1 RC对象系统的介绍
RC是Reference Classes的简称,又称为R5,在R语言的2.12版本被引入,是最新一代的面向对象系统。
RC不同于原来的S3和S4独享系统,RC队形系统得的方法是在类中自定的,而不是泛型函数。RC对象的行为更相似于其他的编程语言,实例化队形的语法也有所改变。
从面向对象的角度来说,我们下面重定义几个名词。
类:面向对象系统的基本类型,类是静态结构定义。
对象:类实例化之后,在内存中生成结构体
方法:是类中的函数定义,不通过泛型函数实现。
5.2 如何创建RC类?
RC对象是以类为基本类型,有专门的类的定义函数setRefClass(),实例化则通过类的方法生成。
5.2.1 setRefClass()
setRefClass(Class,fields=,contains=,methods=,where=,...)
参数列表:
Class: 定义类名
fields: 定义属性和属性类型
contains:定义父类,继承关系
methods: 定义类中的方法
where: 定义存储空间
从setRefClass()函数的定义来看,参数比S4的setClass()函数变少了
5.2.2 创建RC类和实例
# 定义一个RC类
User <- setRefClass("User",fields=list(name="character"))
# 查看User的定义
User
## Generator for class "User":
##
## Class fields:
##
## Name: name
## Class: character
##
## Class Methods:
## "field", "trace", "getRefClass", "initFields", "copy", "callSuper",
## ".objectPackage", "export", "untrace", "getClass", "show",
## "usingMethods", ".objectParent", "import"
##
## Reference Superclasses:
## "envRefClass"
# 实例化一个User对象u1
u1 <- User$new(name='u1')
# 查看u1对对象
u1
## Reference class object of class "User"
## Field "name":
## [1] "u1"
# 检查User类的类型
library(pryr)
class(User)
## [1] "refObjectGenerator"
## attr(,"package")
## [1] "methods"
is.object(User)
## [1] TRUE
otype(User)
## [1] "RC"
# 检查u1的类型
class(u1)
## [1] "User"
## attr(,"package")
## [1] ".GlobalEnv"
is.object(u1)
## [1] TRUE
otype(u1)
## [1] "RC"
5.2.3 创建一个有继承关系的RC类
# 创建RC类User
User <- setRefClass("User",fields=list(name="character"))
# 创建User的子类Member
Member <- setRefClass("Member",contains="User",fields=list(manager="User"))
# 实例化User
manager <- User$new(name="manager")
# 实例化一个son对象
member <- Member$new(name="member",manager=manager)
# 查看Mebmber对象
member
## Reference class object of class "Member"
## Field "name":
## [1] "member"
## Field "manager":
## Reference class object of class "User"
## Field "name":
## [1] "manager"
# 查看member对象的name属性
member$name
## [1] "member"
# 查看member对象的manager属性
member$manager
## Reference class object of class "User"
## Field "name":
## [1] "manager"
# 查看对象的属性类型
otype(member$name)
## [1] "base"
otype(member$manager)
## [1] "RC"
5.2.4 RC对象的默认值
RC类有一个指定构造器方法$initialize(),这个构造器方法在实例化对象时,会自动被运行一次,通过这个构造方法可以设置属性的默认值。
# 定义一个RC类
User <- setRefClass("User",
# 定义两个属性
fields=list(name="character",level="numeric"),
methods = list(initialize=function(name,level){
print("User::initialize")
# 给属性增加默认值
name <<- "conan"
level <<- 1
}))
# 实例化u1
u1 <- User$new()
## [1] "User::initialize"
# 查看对象u1属性被增加了默认值
u1
## Reference class object of class "User"
## Field "name":
## [1] "conan"
## Field "level":
## [1] 1
5.3 对象赋值
# 定义User类
User <- setRefClass("User",fields=list(name='character',age='numeric',gender='factor'))
# 定义一个factor类型
genderFactor <- factor(c('F','M'))
# 实例化u1
u1 <- User$new(name="u1",age=44,gender=genderFactor[1])
# 查看age属性值
u1$age
## [1] 44
给u1的age属性赋值
# 重新赋值
u1$age <- 10
# age属性改变
u1$age
## [1] 10
把u1对象赋给u2对象
# 把u1赋值给u2对象
u2 <- u1
# 查看u2的age属性
u2$age
## [1] 10
# 重新赋值
u1$age <- 20
# 查看u1,u2的age属性都发生了改变
u1$age
## [1] 20
u2$age
## [1] 20
这是由于把u1赋值给u2,传递的是u1的实例化对象的引用,而不是值本身,这一点与其他语言中对象赋值时一样的。
如果想进行赋值而不是引入传递,可以进行下面的操作
# 调用u1的内置方法copy(),赋值给u3
u3 <- u1$copy()
# 查看u3的age属性
u3$age
## [1] 20
# 重新赋值,u3的age属性值并未变化
u1$age <- 30
对引入关系把我,可以减少值传递过程中的内存复制过程,可以让我们的程序运行效率更高。
5.4 定义对象的方法
在S3,S4的对象系统中,我们实现对象行为时,都是借助于泛型函数实现的。这种实现方法的最大问题是:在定义函数和对象的代码是分离的,需要在运行时,通过判断对象的类型完成方法的调用。而RC对象系统中,方法可以定义在类的内部,通过实例化的对象完成方法的调用。
# 定义一个RC类包括方法
User <- setRefClass("User",fields=list(name="character",favorite='vector'),
# 方法属性
methods= list(
# 增加一个兴趣
addFavorite = function(x){
favorite <<- c(favorite,x)
},
# 删除一个兴趣
delFavorite = function(x){
favorite <<- favorite[-which(favorite==x)]
},
# 重新定义兴趣列表
setFavorite = function(x){
favorite <<- x
}
))
# 实例化对象u1
u1 <- User$new(name="u1",favorite=c('movie','football'))
# 查看u1对象
u1
## Reference class object of class "User"
## Field "name":
## [1] "u1"
## Field "favorite":
## [1] "movie" "football"
接下来进行方法操作
# 删除一个兴趣
u1$delFavorite("football")
# 查看兴趣属性
u1$favorite
## [1] "movie"
直接到方法定义到类的内部,通过实例化的对象进行访问。这样就做到了,在定义时就能保证了方法的作用域,减少运行时检查的系统开销。
5.5 RC对象内置方法和内置属性
对于RC的实例化对象,除了我们自己定义的方法函数,还有几个内置的方法。之前属性赋值赋值时使用的copy()方法,就是其中之一
5.5.1 内置方法:
initialize类的初始化函数,用于设置属性的默认值,只有在类定义的方法中使用。
callSuper调用父类的同名方法,只能在类定义的方法中使用
copy复制实例化对象的所有属性
initFields给对象的属性赋值
field查看属性或给属性赋值
getClass查看对象的类定义
getRefClass()同getClass()
show 查看当前对象
export查看属性值以类为作用域
import 把一个对象的属性值赋值给另一个对象
trace跟踪对象中方法调用,用于程序debug
untrace取消跟踪
usingMethods用于实现方法调用,只能在类定义的方法中使用,这个方法不利于程序的健壮性,所以不建议使用。
接下来我们使用这些内置方法。
# 类User
User <- setRefClass("User",
fields=list(name="character",level="numeric"),
methods = list(
initialize=function(name,level){
print("User::initialize")
name <<- "conan"
level <<- 1
},
addLevel = function(x){
print("User::addlevel")
level<<-level+x
},
addHighLevel = function(){
print("user::addHighLevel")
addLevel(2)
}
))
定义子类Member继承父类User,并包括同名方法addLevel覆盖父类的方法,在addLevel方法中,会调用父类的同名方法。
# 子类Member
Member <- setRefClass("Member",contains="User",
# 子类中的属性
fields = list(age='numeric'),
methods=list(
# 覆盖父类的同名方法
addLevel = function(x){
print("Member::addLevel")
callSuper(x)
level <<- level+1
}
)
)
分别实例化对象u1,m1
# 实例化u1
u1 <- User$new(name='u1',level=10)
## [1] "User::initialize"
# 查看u1对象,$new()不能实现赋值操作
u1
## Reference class object of class "User"
## Field "name":
## [1] "conan"
## Field "level":
## [1] 1
# 通过$initFields()向属性赋值
u1$initFields(name='u1',level=10)
## Reference class object of class "User"
## Field "name":
## [1] "u1"
## Field "level":
## [1] 10
# 实例化m1
m1 <- Member$new()
## [1] "User::initialize"
m1$initFields(name='m1',level=100,age=12)
## Reference class object of class "Member"
## Field "name":
## [1] "m1"
## Field "level":
## [1] 100
## Field "age":
## [1] 12
执行$copy()方法,赋值对象属性并传值。
# 属性赋值到u2
u2 <- u1$copy()
## [1] "User::initialize"
使用方法field(),查看并给level属性赋值
# 查看level属性值
u1$field('level')
## [1] 10
# 给level属性值为1
u1$field('level',1)
# 查看level属性值
u1$level
## [1] 1
使用getRefClass()和getClass()方法查看u1对象的类定义。
# 类引入的定义
m1$getRefClass()
## Generator for class "Member":
##
## Class fields:
##
## Name: name level age
## Class: character numeric numeric
##
## Class Methods:
## "addLevel#User", "import", ".objectParent", "usingMethods", "show",
## "getClass", "untrace", "export", ".objectPackage", "callSuper",
## "copy", "initFields", "getRefClass", "trace", "field", "initialize",
## "addLevel", "addHighLevel"
##
## Reference Superclasses:
## "User", "envRefClass"
# 类定义
m1$getClass()
## Reference Class "Member":
##
## Class fields:
##
## Name: name level age
## Class: character numeric numeric
##
## Class Methods:
## "addLevel#User", "import", ".objectParent", "usingMethods", "show",
## "getClass", "untrace", "export", ".objectPackage", "callSuper",
## "copy", "initFields", "getRefClass", "trace", "field", "initialize",
## "addLevel", "addHighLevel"
##
## Reference Superclasses:
## "User", "envRefClass"
# 通过otype查看类型的不同
otype(m1$getRefClass())
## [1] "RC"
otype(m1$getClass())
## [1] "S4"
使用$show()方法查看属性值,$show(),同show()函数,对象直接输出时就是调用了$show()方法
m1$show()
## Reference class object of class "Member"
## Field "name":
## [1] "m1"
## Field "level":
## [1] 100
## Field "age":
## [1] 12
show(m1)
## Reference class object of class "Member"
## Field "name":
## [1] "m1"
## Field "level":
## [1] 100
## Field "age":
## [1] 12
m1
## Reference class object of class "Member"
## Field "name":
## [1] "m1"
## Field "level":
## [1] 100
## Field "age":
## [1] 12
使用$trace()跟踪方法调用,再用$untrace()方法取消跟踪绑定
# 对我addLevel()方法跟踪
m1$trace('addLevel')
## Tracing reference method "addLevel" for object from class
## "Member"
## [1] "addLevel"
# 调用addlevel()方法,tracing m1$addLevel(1)被打印跟踪生效
m1$addLevel(1)
## Tracing m1$addLevel(1) on entry
## [1] "Member::addLevel"
## [1] "User::addlevel"
# 取消对addLevel()方法跟踪
m1$untrace("addLevel")
## Untracing reference method "addLevel" for object from class
## "Member"
## [1] "addLevel"
使用$export()方法,以类作为作用域查看属性
# 查看在member类中的属性
m1$export("Member")
## Reference class object of class "Member"
## Field "name":
## [1] "m1"
## Field "level":
## [1] 102
## Field "age":
## [1] 12
# 查看在User类中的属性,当前作用域不包括age属性
m1$export("User")
## [1] "User::initialize"
## Reference class object of class "User"
## Field "name":
## [1] "m1"
## Field "level":
## [1] 102
使用$import()方法,把一个对象的属性值赋值给另一个对象
# 实例化m2
m2 <- Member$new()
## [1] "User::initialize"
m2
## Reference class object of class "Member"
## Field "name":
## [1] "conan"
## Field "level":
## [1] 1
## Field "age":
## numeric(0)
# 把m1对象的值赋值给m2对象
m2$import(m1)
5.5.2 内置属性
RC对象实例化后,有两个内置属性
.self 实例化对象自身
.refClassDef类的定义类型
# $.self属性
m1$.self
## Reference class object of class "Member"
## Field "name":
## [1] "m1"
## Field "level":
## [1] 102
## Field "age":
## [1] 12
# m1$.self和m1完全相同
identical(m1$.self,m1)
## [1] TRUE
# 查看类型
otype(m1$.self)
## [1] "RC"
# $.refClassDef属性
m1$.refClassDef
## Reference Class "Member":
##
## Class fields:
##
## Name: name level age
## Class: character numeric numeric
##
## Class Methods:
## "addLevel#User", "import", ".objectParent", "usingMethods", "show",
## "getClass", "untrace", "export", ".objectPackage", "callSuper",
## "copy", "initFields", "getRefClass", "trace", "field", "initialize",
## "addLevel", "addHighLevel"
##
## Reference Superclasses:
## "User", "envRefClass"
# 与getClass()相同
identical(m1$.refClassDef,m1$getClass())
## [1] TRUE
# 查看类型
otype(m1$.refClassDef)
## [1] "S4"
5.6 RC类的辅助函数
当定义好了RC类的结构,有一些辅助函数可以帮助我们查看类型的属性和方法,上面用于创建实例化的对象的$new()函数,也属于这类辅助函数
new用于实例化对象
help用于查询类中定义的所有方法
methods列出类中定义的所有方法
fields列出类中定义的所有属性
lock给属性加锁,实例化的对象的属性只允许赋值依次,即赋值变量不可修改
trace跟踪方法
accessors给属性生成get/set方法
接下来,使用辅助函数,继续定义之前的USER类的结构
# 定义User类
User <- setRefClass("User",
fields=list(name="character",level='numeric'),
methods=list(
initialize=function(name,level){
print("User::initialize")
name <<- 'conan'
level <<- 1 },
addLevel = function(x){
print("User::addLevel")
level <<- level+x
},
addHighLevel = function(){
print("User::addhighLevel")
addLevel(2)
}
))
# 实例化对象u1
u1 <- User$new()
## [1] "User::initialize"
# 列出User类中的属性
User$fields()
## name level
## "character" "numeric"
# 列出User类中的方法
User$methods()
## [1] ".objectPackage" ".objectParent" "addHighLevel" "addLevel"
## [5] "callSuper" "copy" "export" "field"
## [9] "getClass" "getRefClass" "import" "initFields"
## [13] "initialize" "show" "trace" "untrace"
## [17] "usingMethods"
# 查看User类中的方法调用
User$help("addLevel")
## Call:
## $addLevel(x)
User$help("show")
## Call:
## $show()
给User类中的属性,增加get/set方法
# 给level属性增加get/set方法
User$accessors("level")
# 列出所有方法
User$methods()
## [1] ".objectPackage" ".objectParent" "addHighLevel" "addLevel"
## [5] "callSuper" "copy" "export" "field"
## [9] "getClass" "getLevel" "getRefClass" "import"
## [13] "initFields" "initialize" "setLevel" "show"
## [17] "trace" "untrace" "usingMethods"
使用$trace()函数,跟踪addLevel方法
使用$lock()函数,把level属性设置为常量
# 锁定level属性
User$lock("level")
# 查看User类中被锁定的属性
User$lock()
## [1] "level"
# 实例化u3,level就被初始化依次
u3 <- User$new()
## [1] "User::initialize"
# 给level属性再次赋值出错
#u3$level = 1
5.7 RC对象实例
我们用RC面向对象的系统做一个例子,定义一套动物叫声模型
5.7.1 任务1:定义动物的数据结构和发声方法
定义animal为动物的基类,包括:猫,狗,鸭
# 创建Animal类,包括name属性,构造方法initialize(),叫声方法bark()
Animal <- setRefClass("Animal",
fields=list(name="character"),
methods=list(
initialize = function(name){
name <<- "Animal"
},
bark = function()print("Animal::bark")
))
# 创建Cat类,继承Animal类,并重写(overwrite
#)了initialize()和bark()
Cat <- setRefClass("Cat",contains="Animal",
methods=list(
initialize = function(name) name <<- 'cat',
bark = function() print(paste(name,"is miao miao"))
))
# 创建Dog类
Dog <- setRefClass("Dog",contains="Animal",
methods=list(
initialize = function(name) name <<- 'Dog',
bark = function() print(paste(name,"is wang wang"))
))
# 创建Duck类
Duck<- setRefClass("Duck",contains="Animal",
methods=list(
initialize = function(name) name <<- 'Duck',
bark = function() print(paste(name,"is ga ga"))
))
接下来,我们实例化对象,然后研究他们的叫声
# 创建cat实例
cat <- Cat$new()
cat$name
## [1] "cat"
# cat叫声
cat$bark()
## [1] "cat is miao miao"
5.7.2 任务2:定义动物的体貌特征
动物的体貌特征包括头,身体,肢,翅膀,我们只增加肢,只是在Animal中添加limbs属性
5.7.3 任务3:定义动物的行动方式
Animal中定义action方法,每一个子类使用callSuper()重写该方法。
通过这个例子,我们应该可以全面的理解R语言中基于RC对象系统的面向对象的程序设计,我本人推荐大家使用RC面向对象,因为这更像是传统语言的面向对象方式。