R语言面向对象编程(第一课)

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

猜你喜欢

转载自blog.csdn.net/weixin_40514680/article/details/80369080
今日推荐