R语言面向对象编程
R6简介
R6是一个单独的R包,与我们熟悉的原生的面向对象系统类型S3,S4和RC类型不一样。在R语言的面向对象系统中,R6类型与RC类型是比较相似的,但R6并不基于S4的对象系统,因此我们在用R6类型开发R包的时候,不用依赖于methods包,而用RC类型开发R包的时候则必须设置methods包的依赖。
R6类型比RC类型更符合其他编程对于面向对象的设置,支持类的公有成员和私有成员,支持函数的主动绑定,并支持跨包的继承关系。由于RC类型的面向对象系统设计并不彻底,所以才会有R6这样的包出现。
library(R6)
function (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)
- classname定义类名(字符串)
- public定义公有成员,包括公有方法和属性(一个列表,一般首先要初始化,用到initialize类似于python中的__init__,可以认为initialize中有几个变量,我们在实例化对象的时候就需要给他传递几个参数。)
- private定义私有成员,包括私有方法和属性。(所谓公有和私有成员,主要区别在于调用的方法上面,公有成员谁都可以调用,私有成员的调用有一定的规则。)
- active主动绑定的函数列表。
- inherit定义父类,继承关系。
- lock是否上锁,如果上锁则用于类变量存储的环境空间被锁定,不能修改。
- class是否把属性封装成对象,默认是封装,如果选择不封装,类中属性存在一个环境空间中。
- portable是否为可移植类型,默认为可移植类型,类中成员访问需要用调用selt和private对象。
- parent_env定义对象的父环境空间。
创建R6类和实例化对象
- 定义一个Person类
Person <- R6Class("Person", # 定义一个R6类
public=list(
hello = function(){ # 定义公有方法hello
print(paste0("Hello"," world!"))
}
)
)
- 查看Person的定义
Person
## <Person> object generator
## Public:
## hello: function ()
## clone: function (deep = FALSE)
## Parent env: <environment: R_GlobalEnv>
## Locked objects: TRUE
## Locked class: FALSE
## Portable: TRUE
实例化Person对象
u1<-Person$new()
u1
## <Person>
## Public:
## clone: function (deep = FALSE)
## hello: function ()
class(u1)
## [1] "Person" "R6"
公有成员和私有成员
类的成员,包括属性和方法2部分。
R6类定义中,可以分开设置公有成员和私有成员。
可以修改Person类的定义,在public参数中增加公有属性name,并通过hello()方法打印name的属性值,让这个R6的类更像是Java语言的JavaBean。在类中访问公有成员时,需要使用self对象进行调用。
Person <- R6Class("Person",
public=list(
name=NULL, # 公有属性
initialize = function(name){ # 构建函数方法
self$name <- name
},
hello = function(){ # 公有方法
print(paste("Hello",self$name))
}
)
)
conan <- Person$new('Conan') # 实例化对象
conan$hello()
## [1] "Hello Conan"
接下来再设置类的私有成员,给Person类中增加private参数。调用私有成员变量时,要通过private对象进行访问。
Person <- R6Class("Person",
private=list( # 私有成员
gender=NULL,
myGender=function(){
print(paste(self$name,"is",private$gender))
}
),
public=list( # 公有成员
name=NULL,
initialize = function(name,gender){
self$name <- name
private$gender<- gender # 给私有属性赋值
},
hello = function(){
print(paste("Hello",self$name))
private$myGender() # 调用私有方法
}
)
)
conan <- Person$new('Conan','Male') # 实例化对象
conan$hello() # 调用用hello()方法
## [1] "Hello Conan"
## [1] "Conan is Male"
R6类的主动绑定
主动绑定(Active bindings)是R6中一种特殊的函数调用方式,把对函数的访问表现为对属性的访问,主动绑定是属于公有成员。在类定义中,通过设置active参数实现主动绑定的功能,给Person类增加两个主动绑定的函数active和rand。
Person <- R6Class("Person",
public = list(
num = 100
),
active = list( # 主动绑定
active = function(value) {
if (missing(value)) return(self$num +10 )
else self$num <- value/2
},
rand = function() rnorm(1)
)
)
conan <- Person$new()
conan$num
## [1] 100
conan$active
## [1] 110
给主动绑定的函数传参数,这里传参数要用赋值符号”<-“,而不能是方法调用”()”。如果主动绑定函数中没有定义参数,则不要向其传递参数(会出错)
conan$active<-100
conan$num
## [1] 50
conan$num
## [1] 50
R6类的继承关系
继承是面向对象的基本特征,R6的面向对象系统也是支持继承的。当创建一个新类时,可以继承已经存在的一个类(做为父类),而后在此基础上新增(公有/私有)成员。
- 定义Person类(作为父类/基类)
Person <- R6Class("Person",
public=list( # 公有成员
name=NULL,
initialize = function(name,gender){
self$name <- name
private$gender <- gender
},
hello = function(){
print(paste("Hello",self$name))
private$myGender()
}
),
private=list( # 私有成员
gender=NULL,
myGender=function(){
print(paste(self$name,"is",private$gender))
}
)
)
Worker <- R6Class("Worker",
inherit = Person, # 继承,指向父类
public=list(
bye = function(){ # 新增公有方法bye
print(paste("bye",self$name))
}
)
)
u2 <- Worker$new("Conan","Male") # 建立Worker类的实例u2
u2$hello() # 调用hello()方法,此方法是从父类继承而来
## [1] "Hello Conan"
## [1] "Conan is Male"
u2$name #调用name成员,此成员为父类的公有属性(是继承过来的)
## [1] "Conan"
u2$bye() #调用bye成员,此成员为自己的公有方法
## [1] "bye Conan"
- 修改Worker类,在子类定义与父类同样的private属性和方法。
Worker <- R6Class("Worker",
inherit = Person,
public=list(
bye = function(){
print(paste("bye",self$name))
}
),
private=list(
gender=NULL,
myGender=function(){
print(paste("worker",self$name,"is",private$gender))
}
)
)
u2<-Worker$new("Conan","Male") #创建Worker类的实例
u2$hello() # 调用hello()方法
## [1] "Hello Conan"
## [1] "worker Conan is Male"
由于子类中的myGender()私有方法,覆盖了父类的myGender()私有方法,所以在调用hello()方法时,hello()方法中会调用子类中的myGender()方法实现,而忽略了父类中的myGender()方法。
如果在子类中想调用父类的方法,有一个办法是使用super对象,通过super$xx()的语法进行调用。
Worker <- R6Class("Worker",
inherit = Person,
public=list(
bye = function(){
print(paste("bye",self$name))
}
),
private=list(
gender=NULL,
myGender=function(){
super$myGender()
print(paste("worker",self$name,"is",private$gender))
}
)
)
u2<-Worker$new("Conan","Male")
u2$hello()
R6类的动态绑定
A <- R6Class("A",
private = list(
x = NULL
),
public = list(
initialize = function(value){
private$x <- ifelse(missing(value),0,value)
},
getx = function(){ #获取x的值
private$x
}
)
)
a1 <- A$new(10)
a1$getx()
## [1] 10
如果想获取x的平方,该如何办呢?
重新改写A类的定义?这种方法可行,但还有一种方法:动态绑定!
# 动态增加getx2()方法
A$set("public", "getx2", function() private$x**2, overwrite = T)
a2 <- A$new(5)
a2$getx()
## [1] 5
a2$getx2()
## [1] 25
# 如果不重新创建a1实例,而直接访问刚动态绑定的getx2,会出错(这个地方留意
# a1$getx2() )
a1 <- A$new(10)
a1$getx2()
## [1] 100