第 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面向对象,因为这更像是传统语言的面向对象方式。