R语言基于R6的面向对象编程
R的极客理想系列文章,涵盖了R的思想,使用,工具,创新等的一系列要点,以我个人的学习和体验去诠释R的强大。
R语言作为统计学一门语言,一直在小众领域闪耀着光芒。直到大数据的爆发,R语言变成了一门炙手可热的数据分析的利器。随着越来越多的工程背景的人的加入,R语言的社区在迅速扩大成长。现在已不仅仅是统计领域,教育,银行,电商,互联网….都在使用R语言。
要成为有理想的极客,我们不能停留在语法上,要掌握牢固的数学,概率,统计知识,同时还要有创新精神,把R语言发挥到各个领域。让我们一起动起来吧,开始R的极客理想。
关于作者:
- 张丹(Conan), 程序员Java,R,PHP,Javascript
- weibo:@Conan_Z
- blog: http://blog.fens.me
- email: bsspirit@gmail.com
转载请注明出处:
http://blog.fens.me/r-class-r6/
前言
R6是什么?听说过S3、S4和RC(R5)的面向对象类型 ,R6难道是一种新的类型吗?
其实,我也是在无意中发现R6的。R6是R语言的一个面向对象的R包,R6类型非常接近于RC类型(Reference classes),但比RC类型更轻,由于R6不依赖于S4的对象系统,所以用R6的构建面向对象系统会更加有效率。
目录
- 初识R6
- 创建R6类和实例化对象
- R6类的主动绑定
- R6类的继承关系
- R6类的对象的静态属性
- R6类的可移植类型
- R6类的动态绑定
- R6类的打印函数
- 实例化对象的存储
- R6面向对象系统的案例
1. 初识R6
R6是一个单独的R包,与我们熟悉的原生的面向对象系统类型S3,S4和RC类型不一样。在R语言的面向对象系统中,R6类型与RC类型是比较相似的,但R6并不基于S4的对象系统,因此我们在用R6类型开发R包的时候,不用依赖于methods包,而用RC类型开发R包的时候则必须设置methods包的依赖,在发布gridgame游戏包 文章中,就会出现RC依赖于methods包的使用情况。
R6类型比RC类型更符合其他编程对于面向对象的设置,支持类的公有成员和私有成员,支持函数的主动绑定,并支持跨包的继承关系。由于RC类型的面向对象系统设计并不彻底,所以才会有R6这样的包出现。下面就让我们体会一下,基于R6面向对象系统编程吧。
2. 创建R6类和实例化对象
本文的系统环境
- Win7 64bit
- R: 3.1.1 x86_64-w64-mingw32/x64 (64-bit)
我们先安装R6包,同时为了方便我们检查对象的类型,引入pryr包作为辅助工具。关于pryr包的介绍,请参看撬动R内核的高级工具包pryr一文。
~ R # 启动R程序
> install.packages("R6") # 安装R6包
> library(R6) # 加载R6包
> library(pryr) # 加载pryr包
注:R6同时支持Win7环境和Linux环境
2.1 如何创建R6类?
R6对象系统是以类为基本类型, 有专门的类的定义函数 R6Class() 和 实例化对象的生成方法,下面我们用R6对象系统创建一个类。
先查看R6的类创建函数R6Class()函数的定义。
> R6Class
function (classname = NULL, public = list(), private = NULL,active = NULL, inherit = NULL, lock = TRUE, class = TRUE,portable = TRUE, parent_env = parent.frame())
参数列表:
- classname 定义类名。
- public 定义公有成员,包括公有方法和属性。
- private 定义私有成员,包括私有方法和属性。
- active 主动绑定的函数列表。
- inherit 定义父类,继承关系。
- lock 是否上锁,如果上锁则用于类变量存储的环境空间被锁定,不能修改。
- class 是否把属性封装成对象,默认是封装,如果选择不封装,类中属性存存在一个环境空间中。
- portable 是否为可移植类型,默认是可移植型类,类中成员访问需要用调用self和private对象。
- parent_env 定义对象的父环境空间。
从R6Class()函数的定义来看,参数比RC类定义函数的setRefClass()函数有更多的面对对象特征。
2.2 创建R6的类和实例化对象
我们先创建一个最简单的R6的类,只包括一个公有方法。
> Person <- R6Class("Person", # 定义一个R6类
+ public=list(
+ hello = function(){ # 定义公有方法hello
+ print(paste("Hello"))
+ }
+ )
+)> Person # 查看Person的定义
<Person> object generatorPublic:hello: functionParent env: Lock: TRUEPortable: TRUE> class(Person) # 检查Person的类型
[1] "R6ClassGenerator"
接下来,实例化Person对象,使用$new()函数完成。
> u1<-Person$new() # 实例化一个Person对象u1
> u1 #查看u1对象
<Person>Public:hello: function
> class(u1) # 检查u1的类型
[1] "Person" "R6"
通过pryr包的otype检查Person类的类型和u1对象的实例化类型。
> otype(Person) # 查看Person类型
[1] "S3"
> otype(u1) # 查看u1类型
[1] "S3"
完全没有想到的结果,Person和u1都是S3类型的。如果R6是基于S3系统构建的,那么其实就可以解释R6类型与RC类型的不同,并且R6在传值和继承上会更有效率。
2.3 公有成员和私有成员
类的成员,包括属性和方法2部分。R6类定义中,可以分开设置公有成员和私有成员。我们设置类的公有成员,修改Person类的定义,在public参数中增加公有属性name,并通过hello()方法打印name的属性值,让这个R6的类更像是Java语言的JavaBean。在类中访问公有成员时,需要使用self对象进行调用。
> Person <- R6Class("Person",
+ public=list(
+ name=NA, # 公有属性
+ initialize = function(name){ # 构建函数方法
+ self$name <- name
+ },
+ hello = function(){ # 公有方法
+ print(paste("Hello",self$name))
+ }
+ )
+)> conan <- Person$new('Conan') # 实例化对象
> conan$hello() # 调用用hello()方法
[1] "Hello Conan"
接下来再设置类的私有成员,给Person类中增加private参数,并在公有函数有调用私有成员变量,调用私有成员变量时,要通过private对象进行访问。
> Person <- R6Class("Person",
+ public=list( # 公有成员
+ name=NA,
+ initialize = function(name,gender){
+ self$name <- name
+ private$gender<- gender # 给私有属性赋值
+ },
+ hello = function(){
+ print(paste("Hello",self$name))
+ private$myGender() # 调用私有方法
+ }
+ ),
+ private=list( # 私有成员
+ gender=NA,
+ myGender=function(){
+ print(paste(self$name,"is",private$gender))
+ }
+ )
+ )
> conan <- Person$new('Conan','Male') # 实例化对象
> conan$hello() # 调用用hello()方法
[1] "Hello Conan"
[1] "Conan is Male"
在给Person类中增加私有成员时,通过private参数定义gender的私有属性和myGender()的私有方法。之得注意的是在类的内部,要访问私有成员时,需要用private对象进行调用。
那我直接访问公有属性和私有属性时,公有属性返回正确,而私有属性就是NULL值,并且访问私有方法不可见。
> conan$name # 公有属性
[1] "Conan"
> conan$gender # 私有属性
NULL
> conan$myGender() # 私有方法
Error: attempt to apply non-function
进一步地,我们看看self对象和private对象,具体是什么。在Person类中,增加公有方法member(),在member()方法中分别打印self对象和private对象。
> Person <- R6Class("Person",
+ public=list(
+ name=NA,
+ initialize = function(name,gender){
+ self$name <- name
+ private$gender<- gender
+ },
+ hello = function(){
+ print(paste("Hello",self$name))
+ private$myGender()
+ },
+ member = function(){ # 用于测试的方法
+ print(self)
+ print(private)
+ print(ls(envir=private))
+ }
+ ),
+ private=list(
+ gender=NA,
+ myGender=function(){
+ print(paste(self$name,"is",private$gender))
+ }
+ )
+ )
>
> conan <- Person$new('Conan','Male')
> conan$member() # 执行member()方法
<Person> # print(self)的输出Public:hello: functioninitialize: functionmember: functionname: Conan<environment: 0x0000000008cfc918> # print(private)的输出
[1] "gender" "myGender" # print(ls(envir=private))的输出
从测试结果,我们可以看出self对象,就是实例化的对象本身。private对象则是一个的环境空间,是self对象所在环境空间的中的一个子环境空间,所以私有成员只能在当前的类中被调用,外部访问私有成员时,就会找不到。在环境空间中保存私有成员的属性和方法,通过环境空间的访问控制让外部调用无法使用私有属性和方法,这种方式是经常被用在R包开发上的技巧。
3. 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 # 调用主动绑定的active()函数,结果为 num +10= 100+10=100
[1] 110
给主动绑定的active函数传参数,这里传参数要用赋值符号”<-",而不能是方法调用"()"。
> conan$active<-100 # 传参数
> conan$num # 查看公有属性num
[1] 50
> conan$active # 调用主动绑定的active()函数,结果为 num+10=50+10=60
[1] 60
> conan$active(100) # 如果进行方法调用,其实会提示没有这个函数的
Error: attempt to apply non-function
我们再来调用rand函数,看看执行情况。
> conan$rand # 调用rand函数
[1] -0.4767338
> conan$rand
[1] 0.1063623
> conan$rand<-99 # 传参出错
Error in (function () : unused argument (quote(99))
我们直接使用rand()函数完全没有问题,但给rand()函数传参数的时候就出现了错误,由于rand()函数没有定义参数,所以这个操作是不允许的。
通过主动绑定,可以把函数的行为转换成属性的行为,让类中的函数操作更加灵活。
4. R6类的继承关系
继承是面向对象的基本特征,R6的面向对象系统也是支持继承的。当你创建一个类时,可以继承另一个类做为父类存在。
先创建一个父类Person,包括公有成员和私有成员。
> Person <- R6Class("Person",
+ public=list( # 公有成员
+ name=NA,
+ initialize = function(name,gender){
+ self$name <- name
+ private$gender <- gender
+ },
+ hello = function(){
+ print(paste("Hello",self$name))
+ private$myGender()
+ }
+ ),
+ private=list( # 私有成员
+ gender=NA,
+ myGender=function(){
+ print(paste(self$name,"is",private$gender))
+ }
+ )
+ )
创建子类Worker继承父类Person,并在子类增加bye()公有方法。
> Worker <- R6Class("Worker",
+ inherit = Person, # 继承,指向父类
+ public=list(
+ bye = function(){
+ print(paste("bye",self$name))
+ }
+ )
+ )
实例化父类和子类,看看继承关系是不是生效了。
> u1<-Person$new("Conan","Male") # 实例化父类
> u1$hello()
[1] "Hello Conan"
[1] "Conan is Male"> u2<-Worker$new("Conan","Male") # 实例化子类
> u2$hello()
[1] "Hello Conan"
[1] "Conan is Male"
> u2$bye()
[1] "bye Conan"
我们看到继承确实生效了,在子类中我们并没有定义hello()方法,子类实例u2可以直接使用hello()方法。同时,子类u2的bye()方法,到了在父类中定义的name属性,输出结果完全正确的。
接下来,我们在子类中定义父类的同名方法,然后再查看方法的调用,看看是否会出现继承中函数重写的特征。修改Worker类,在子类定义private的属性和方法。
> Worker <- R6Class("Worker",
+ inherit = Person,
+ public=list(
+ bye = function(){
+ print(paste("bye",self$name))
+ }
+ ),
+ private=list(
+ gender=NA,
+ myGender=function(){
+ print(paste("worker",self$name,"is",private$gender))
+ }
+ )
+ )
实例化子类,调用hello()方法。
> u2<-Worker$new("Conan","Male")
> 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=NA,
+ myGender=function(){
+ super$myGender() # 调用父类的方法
+ print(paste("worker",self$name,"is",private$gender))
+ }
+ )
+ )> u2<-Worker$new("Conan","Male")
> u2$hello()
[1] "Hello Conan"
[1] "Conan is Male"
[1] "worker Conan is Male"
在子类myGender()方法中,用super对象调用父类的myGender()方法,从输出可以看出,父类的同名方法也同时被调用了。
5. R6类的对象的静态属性
用面向对象的方法进行编程,那么所有变量其实都是对象,我们可以把一个实例化的对象定义成另一个类的属性,这样就形成了对象的引用关系链。
需要注意的一点是,当属性赋值为另一个R6的对象时,属性的值保存了对象的引用,而非对象实例本身。利用这个规则就可以实现对象的静态属性,也就是可以在多种不同的实例中是共享对象属性,类似于Java中的static属性一样。
下面用代码描述一下,就能很容易的理解。定义两个类A和B,A类中有一个公有属性x,B类中有一个公有属性a,a为A类的实例化对象。
> A <- R6Class("A",
+ public=list(
+ x = NULL
+ )
+ )
>
> B <- R6Class("B",
+ public = list(
+ a = A$new()
+ )
+ )
运行程序,实现B实例化对象对A实例化对象的调用,并给x变量赋值。
> b <- B$new() # 实例化B对象
> b$a$x <- 1 # 给x变量赋值
> b$a$x # 查看x变量的值
[1] 1> b2 <- B$new() # 实例化b2对象
> b2$a$x <- 2 # 给x变量赋值
> b2$a$x # 查看x变量的值
[1] 2> b$a$x # b实例的a对象的x值也发生改变
[1] 2
从输出结果可以看到,a对象实现了在多个b实例的共享,当b2实例修改a对象x值的时候,b实例的a对象的x值也发生了变化。
这里有一种写法,我们是应该要避免的,就是通过initialize()方法赋值。
> C <- R6Class("C",
+ public = list(
+ a = NULL,
+ initialize = function() {
+ a <<- A$new()
+ }
+ )
+ )> cc <- C$new()
> cc$a$x <- 1
> cc$a$x
[1] 1> cc2 <- C$new()
> cc2$a$x <- 2
> cc2$a$x
[1] 2> cc$a$x # x值未发生改变
[1] 1
通过initialize()构建是的a对象,是对单独的环境空间中的引用,所以不能实现引用对象的共享。
6. R6类的可移植类型
在R6类的定义中,portable参数可以设置R6类的类型为可移植类型和不可移植类型。可移植类型和不可移植类型主要有2个明显的特征。
- 可移植类型支持跨R包的继承;不可移植类型,在跨R包继承的时候,兼容性不太好。
- 可移植类型必须要用self对象和private对象来访问类中的成员,如self$x,private$y;不可移植类型,可以直接使用变量x,y,并通过<<-实现赋值。
本文中使用的是R6的最新版本2.0,所以默认创建的是可移植类型。所以,当我们要考虑是否有跨包继承的需求时,可以在可移植类型和不可移植类型之间进行选择。
我们比较一下RC类型,R6的可移植类型和R6的不可移植类型三者的区别,定义一个简单的类,包括一个属性x和两个方法getx(),setx()。
> RC <- setRefClass("RC", # RC类型的定义
+ fields = list(x = 'Hello'),
+ methods = list(
+ getx = function() x,
+ setx = function(value) x <<- value
+ )
+ )
> rc <- RC$new()
> rc$setx(10)
> rc$getx()
[1] 10
创建一个行为完全一样的不可移植类型的R6类。
> NR6 <- R6Class("NR6", # R6不可移植类型
+ portable = FALSE,
+ public = list(
+ x = NA,
+ getx = function() x,
+ setx = function(value) x <<- value
+ )
+ )
> np6 <- NR6$new()
> np6$setx(10)
> np6$getx()
[1] 10
再创建一个行为完全一样的可移植类型的R6类。
> PR6 <- R6Class("PR6",
+ portable = TRUE, # R6可移植类型
+ public = list(
+ x = NA,
+ getx = function() self$x,
+ setx = function(value) self$x <- value
+ )
+ )
> pr6 <- PR6$new()
> pr6$setx(10)
> pr6$getx()
[1] 10
从这个例子中,可移植类型的R6类和不可移植类型的区别在,就在于self对象的使用。
7. R6类的动态绑定
对于静态类型的编程语言来说,一旦类定义后,就不能再修改类中的属性和方法,像反射这样的高开销的特殊操作除外。 而对于动态类型的编程语言来说,通常不存在这样的限制,可以任意修改其类的结构或者已实例化的对象的结构。 R作为动态语言来说,同样是支持动态变量修改的,基于S3类型和S4类型可以通过泛型函数动态地增加函数定义,但RC类型是不支持的,再次感觉到了R语言中面向对象系统设计的奇葩了。
R6包已考虑这个情况,提供了一种动态设置成员变量的方法用$set()函数。
> A <- R6Class("A",
+ public = list(
+ x = 1,
+ getx = function() x
+ )
+ )
> A$set("public", "getx2", function() self$x*2) # 动态增加getx2()方法
> s <- A$new()
> s # 查看实例化对象的结构
<A>Public:getx: functiongetx2: functionx: 1
> s$getx2() # 调用getx2()方法
[1] 20
同样地,属性也可以动态修改,动态改变x属性的值。
> A$set("public", "x", 10, overwrite = TRUE) # 动态改变x属性
> s <- A$new()
> s$x # 查看x属性
[1] 10
> s$getx() # 调用getx()方法,可移植类型x变量丢失
Error in s$getx() : object 'x' not found
由于A类默认是可移植类型的,所以在使用x变量时应该通过self对象来访问,否则动态成员修改的时候,就会出现错误,我们把getx中的x改为self$x。
> A <- R6Class("A",
+ public = list(
+ x = 1,
+ getx = function() self$x # 修改为self$x
+ )
+ )
> A$set("public", "x", 10, overwrite = TRUE)
> s <- A$new()
> s$x
[1] 10
> s$getx() # 调用getx()方法
[1] 10
对于可移植类型和不可移植类型,建议大家养成习惯都使用self和private对象进行访问。
8. R6类的打印函数
R6提供了用于打印的默认方法print(),每当要打印实例化对象时,都会调用这个默认的print()方法,有点类似于Java类中默认的toString()方法。
我们可以覆盖print()方法,使用自定义的打印提示。
> A <- R6Class("A",
+ public = list(
+ x = 1,
+ getx = function() self$x
+ )
+ )
> a <- A$new()
> print(a) # 使用默认的打印方法
<A>Public:getx: functionx: 1
自定义打印方法,覆盖print()方法。
> A <- R6Class("A",
+ public = list(
+ x = 1,
+ getx = function() self$x,
+ print = function(...) {
+ cat("Class <A> of public ", ls(self), " :", sep="")
+ cat(ls(self), sep=",")
+ invisible(self)
+ }
+ )
+ )
> a <- A$new()
> print(a)
Class <A> of public getxprintx :getx,print,x
通过自定义的方法,就可以覆盖系统默认的方法,从而输出我们想显示的文字。
9. 实例化对象的存储
R6是基于S3面向对象系统的构建,而S3类型又是一种比较松散的类型,会造成用户环境空间的变量泛滥的问题。R6提供了一种方式,设置R6Class()的class参数,把类中定义的属性和方法统一存储到一个S3对象中,这种方式是默认的。另一种方式为,把把类中定义的属性和方法统一存储到一个单独的环境空间中。
我们看查看一下默认的情况,class=TRUE,实例化后的a对象,就是一个S3的类。
> A <- R6Class("A",
+ class=TRUE,
+ public = list(
+ x = 1,
+ getx = function() self$x
+ )
+ )
> a <- A$new()
> class(a)
[1] "A" "R6"
> a
<A>Public:getx: functionx: 1
当class=FALSE时,实例化后的a对象,是一个环境空间,在环境空间中存储了类的变量数据。
> B <- R6Class("B",
+ class=FALSE,
+ public = list(
+ x = 1,
+ getx = function() self$x
+ )
+ )
> b <- B$new()
> class(b)
[1] "environment"
> b
<environment: 0x000000000d83c970>
> ls(envir=b)
[1] "getx" "x"
实例化对象的存储还有另外一方面的考虑,由于类中的变量都是存在于一个环境空间中的,我们也可以通过手动的方式找到这个环境空间,从而进行变量的增加或修改。 如果随意地对环境空间中的变量进行修改,那么会给我们的程序带来一些安全上的风险,所以为了预防安全上的问题,可以通过R6Class()的lock参数所定环境空间,不允许动态修改,默认值为锁定状态不能修改。
> A <- R6Class("A",
+ lock=TRUE, # 锁定环境空间
+ public = list(
+ x = 1
+ )
+ )
> s<-A$new()
> ls(s) # 查看s环境空间的变量
[1] "x"
> s$aa<-11 # 增加新变量,错误
Error in s$aa <- 11 : cannot add bindings to a locked environment
> rm("x",envir=s) # 删除原有变量,错误
Error in rm("x", envir = s) :cannot remove bindings from a locked environment
如果不锁定环境空间,让lock=FALSE,则环境空间处于完全开放的状态,可以任意进行变量的修改。
> A <- R6Class("A",
+ lock=FALSE, # 不锁定环境空间
+ public = list(
+ x = 1
+ )
+ )
> s<-A$new()
> ls(s) # 查看s环境空间的变量
[1] "x"
> s$aa<-11 # 增加变量
> ls(s)
[1] "aa" "x"
> rm("x",envir=s) # 删除变量
> ls(s)
[1] "aa"
通过上面对R6的介绍,我就基本掌握R6面向对象系统的知识。接下来,我们做一个简单的例子,应用一下R6的面向对象编程。
10. R6面向对象系统的案例
我们用R6的面向对象系统,来构建一个图书分类的使用案例。
任务一:定义图书的静态结构
以图书(book)为父类,包括R,Java,PHP 的3个分类,在book类中定义私有属性及公有方法。
定义图书系统的数据结构,包括父类的结构 和 3种型类的图书。
> Book <- R6Class("Book", # 父类
+ private = list(
+ title=NA,
+ price=NA,
+ category=NA
+ ),
+ public = list(
+ initialize = function(title,price,category){
+ private$title <- title
+ private$price <- price
+ private$category <- category
+ },
+ getPrice=function(){
+ private$price
+ }
+ )
+ )> R <- R6Class("R", # 子类R图书
+ inherit = Book
+ )
> Java <- R6Class("JAVA", # 子类JAVA图书
+ inherit = Book
+ )
> Php <- R6Class("PHP", # 子类PHP图书
+ inherit = Book
+ )
创建3个实例化对象,R语言图书《R的极客理想-工具篇》,JAVA语言图书《Java编程思想》,PHP语言图书《Head First PHP & MySQL》,并获得图书的定价。
> r1<-R$new("R的极客理想-工具篇",59,"R")
> r1$getPrice()
[1] 59> j1<-Java$new("Java编程思想",108,"JAVA")
> j1$getPrice()
[1] 108> p1<-Java$new("Head First PHP & MySQL",98,"PHP")
> p1$getPrice()
[1] 98
任务二:正逢双11对各类图书打折促销
我们设计一种打折规则,用来促进图书的销售,这个规则纯属虚构。
- 所有图书9折
- JAVA图书7折,不支持重复打折
- 为了推动R图书的销售,R语言图书7折,并支持重复打折
- PHP图书无特别优惠
根据打折规则,图书都可以被打折,那么打折就可以作为图书对象的一个行为,然后R, Java, PHP的3类图书,分别还有自己的打折规则,所以是一种多态的表现。
我们修改父类的定义,增加打折的方法discount(),默认设置为9折,满足第一条规则。
> Book <- R6Class("Book",
+ private = list(
+ title=NA,
+ price=NA,
+ category=NA
+ ),
+ public = list(
+ initialize = function(title,price,category){
+ private$title <- title
+ private$price <- price
+ private$category <- category
+ },
+ getPrice=function(){
+ p<-private$price*self$discount()
+ print(paste("Price:",private$price,", Sell out:",p,sep=""))
+ },
+ discount=function(){
+ 0.9
+ }
+ )
+ )
3个子类,分别对应自己的打折规则,分别进行修改。
- 给JAVA子类增加 discount()方法,用于覆盖父类的discount()方法,让JAVA图书7折,不支持重复打折,从而满足第二条规则。
- 给R子类增加 discount()方法,在子类的discount()方法中调用父类的discount()方法,让支持 R图书7折和9折的折上折,从而满足第三条规则。
- PHP子类,没有修改,完全遵循第一条规则的。
> Java <- R6Class("JAVA", + inherit = Book, + public = list( + discount=function(){ + 0.7 + } + ) + ) > > R <- R6Class("R", + inherit = Book, + public = list( + discount=function(){ + super$discount()*0.7 + } + ) + ) > > Php <- R6Class("PHP", + inherit = Book + )
分别查看3本图书的折后价格。
> r1<-R$new("R的极客理想-工具篇",59,"R") > r1$getPrice() [1] "Price:59, Sell out:37.17" # 59 * 0.9 *0.7= 37.17 > > j1<-Java$new("Java编程思想",108,"JAVA") > j1$getPrice() [1] "Price:108, Sell out:75.6" # 108 *0.7= 75.6 > > p1<-Php$new("Head First PHP & MySQL",98,"PHP") > p1$getPrice() [1] "Price:98, Sell out:88.2" # 98 *0.9= 88.2
R图书打折最多,享受7折和9折的折上折优惠, 59 * 0.9 * 0.7= 37.17;Java图书享受7折优惠,108 *0.7= 75.6;PHP图书享受9折优惠 98 *0.9= 88.2。
通过这个实例,我们用R6的方法实现了面向对象编程中的封装、继承和多态的3个特征,证明R6是一种完全的面向对象的实现。R6类对象系统,提供了一种可兼容的面向对象实现方式,更接近于其他的编程语言上的面向对象的定义,由于R6底层基于S3来实现的,所以比RC的类更加有效果。
我们一共介绍了4种R语言的面向对象体系结构,选择自己理解的,总有一种会适合你。
转载请注明出处:
http://blog.fens.me/r-class-r6/
R语言基于R6的面向对象编程相关推荐
- R开发(part12)--基于RC的面向对象编程
学习笔记,仅供参考,有错必纠 参考自:<R的极客理想>-- 张丹 文章目录 R开发 基于RC的面向对象编程 创建RC类和对象 对象赋值 定义对象的方法 RC对象内置方法和内置属性 RC类的 ...
- R开发(part11)--基于S4的面向对象编程
学习笔记,仅供参考,有错必纠 参考自:<R的极客理想>-- 张丹 文章目录 R开发 基于S4的面向对象编程 创建S4对象 访问S4对象的属性 S4的泛型函数 查看S4对象的函数 R开发 基 ...
- R开发(part10)--基于S3的面向对象编程
学习笔记,仅供参考,有错必纠 参考自:<R的极客理想>-- 张丹 文章目录 R开发 面向对象 面向对象的R语言实现 基于S3的面向对象编程 泛型函数和方法调用 查看S3对象的函数 S3对象 ...
- 基于MATLAB的面向对象编程(1)——类,属性,方法
这里写目录标题 1 入门 1.1 如何把面条抽象成Class 1.2 文件类 2 基于MATLAB的面向对象编程入门 2.1 如何定义一个类(Class) 2.2 创建一个对象 2.3 类的属性 2. ...
- 基于对象和面向对象编程范式辨析和主流编程语言中的应用
基于对象和面向对象编程范式辨析和主流编程语言中的应用 前言 本文的目的是想告诉大家,为什么C++的模板这么强大.为什么Ruby的Duck Typing(像鸭子那样编程)这么强大! 基于对象和面向对象编 ...
- 各个省肺炎感染类型及人数可视化R语言基于地理位置进行分面
(各个省肺炎感染类型及人数可视化)R语言基于地理位置进行分面 不知道大家有没有看过这样的图:乍一看还以为是元素周期表(哈哈哈,化学都还给老师了).实际上他这个是根据美国各个州的地理位置进行分面(在R语 ...
- R语言基于日期范围筛选数据实战(Subset by a Date Range):日期范围之内的数据、日期范围之外的数据、日期之后的数据、日期之前的数据
R语言基于日期范围筛选数据实战(Subset by a Date Range):日期范围之内的数据.日期范围之外的数据.日期之后的数据.日期之前的数据 目录 R语言基于日期范围筛选数据实战(Subse ...
- R语言基于可视化进行多变量离群(Mulltivariate outliers)点检测识别:散点图可视化多变量离群点、模型平滑多变量异常检测、使用平行坐标图查看钻石数据集中的异常值
R语言基于可视化进行多变量离群(Mulltivariate outliers)点检测识别:散点图可视化多变量离群点.模型平滑多变量异常检测.使用平行坐标图查看钻石数据集中的异常值 目录
- R语言基于多字段(多数据列、multiple columns)对dataframe的行数据进行排序(Ordering rows)实战:使用R原生方法、data.table、dplyr等方案
R语言基于多字段(多数据列.multiple columns)对dataframe的行数据进行排序(Ordering rows)实战:使用R原生方法.data.table.dplyr等方案 目录
最新文章
- c语言读文件一行为一个数组元素,c语言数组练习习题(14页)-原创力文档
- [Python unittest] 3-Organizing test code
- 设计出python_Python: 实际项目中抽象出的小项目设计
- wpf中groupbox有什么用_环境中的硫化氢用什么检测好
- Linux java 生效不了,linux jdk 不生效怎么办
- Docker小结(五)
- SQLSERVER索引汇总
- 可以完成99%的静态页面的HTML标签
- 一个简单的nginx配置
- vue 响应式布局组件_今天如何使用响应式Web组件
- Word2vec简单整理
- 五款免费同步备份软件介绍
- 为什么说串行比并行快?
- luoguP5055 【模板】可持久化文艺平衡树 可持久化非旋转treap
- MyBatis基于XML的使用——缓存
- 串口通信--两台PC机之间的简单传输
- 强制重启计算机快捷键,强制重启电脑快捷键
- Linux中用maven创建一个简单的项目
- Leetcode刷题 2021.01.22
- R语言数学函数及统计函数及概率函数
热门文章
- 任务二:停电停多久问题关键算法
- 微信输入法上线!再也不怕隐私被盗
- mysql连接字符集设置
- 【测试面试】自我分析+功能+接口自动化+性能测试面试题(大全),知己知彼百战百胜......
- DbgHelp应用程序的开发(二)
- python图像分类整理_python常见图形代码可视化大全整理(包括动图)更新中...
- fisco bcos区块链的同步及其性能优化方法
- Yii2 ajax的post请求Csrf验证失败
- Appium+python OPPO手机避坑指南
- STM32—0.96寸OLED液晶显示