R 面向对象编程(四)—— R6

2021-03-23  本文已影响0人  名本无名

R6

4.1 介绍

R6R 的封装式面向对象编程的实现,比内置的 RC 类更简单,更快,更轻量级。

与内置的 R3R4RC 不同,R6 是一个单独的 R 包,因此不需要依赖 methods 包。

R6 类支持:

为什么这个包叫 R6 呢?

哈哈,当然是为了保持队形了啊

S3、S4、S5、S6,虽然 RC 的官方名称并不是 S5,但不妨碍大家这么称呼。

学过其他语言的面向对象编程系统的应该知道,我们前面几节讲的 R 中几种系统设计的并不够好,所以,需要 R6 这样的包。

4.2 创建 R6 对象

R6 是第三方包,所以记得先安装一下

install.packages("R6")
library(R6)

R6 是通过 R6Class() 函数创建类

R6Class(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)

参数列表

创建一个简单的 Person

Person <- R6Class(
  "Person",
  public = list(
    name = NA,
    initialize = function(name) {
      self$name <- name
    },
    say = function() {
      cat("my name is ", self$name)
    }
  )
)

创建实例,同样使用 $new 方法来实例化

> tom <- Person$new(name = "tom")
> tom
<Person>
  Public:
    clone: function (deep = FALSE) 
    initialize: function (name) 
    name: tom
    say: function () 

查看类与实例的类型

> class(Person)
[1] "R6ClassGenerator"
> class(tom)
[1] "Person" "R6"    
> otype(tom)
[1] "S3"
> otype(Person)
[1] "S3"

我们可以看到,其实 R6 系统是基于 S3 构建的,这也是它不同于 RC 的原因

4.3 公有成员与私有成员

R6 系统的类定义中,可以设置公有成员和私有成员。这一特征与 JavaC++ 的类很像,使用私有成员来隐藏一些数据属性和方法。

R6 中公有成员的访问使用的是 self 对象来引用,而私有需要用 private 对象来引用。

在前面的例子中,我们使用的是 self$name 来获取公有属性 name,现在让我们来添加私有成员

Person <- R6Class(
  "Person",
  public = list(
    name = NA,
    initialize = function(name, money) {
      self$name <- name
      private$money <- money
    },
    say = function() {
      cat("my name is ", self$name)
    },
    incSalary = function(percent) {
      private$setMoney(private$money * (1 + percent))
      invisible(self)
    }
  ),
  private = list(
    money = NA,
    setMoney = function(m) {
      cat(paste0("change ", self$name, "'s salary!\n"))
      private$money <- m
    }
  )
)

我们添加了私有属性 money 和私有函数 setMoney

我们先创建一个实例化对象

> tom <- Person$new(name = "tom", 1000)
> tom
<Person>
  Public:
    clone: function (deep = FALSE) 
    incSalary: function (percent) 
    initialize: function (name, money) 
    name: tom
    say: function () 
  Private:
    money: 1000
    setMoney: function (m) 

然后调用对应的方法

> tom$name
[1] "tom"
> tom$money
NULL
> tom$incSalary(0.1)
change tom's salary!
> tom$setMoney
NULL
> tom$setMoney()
错误: 不适用于非函数

我们可以使用 $ 符号正常访问公有成员,但是无法访问私有成员

注意:我们在 incSalary 函数中添加了一行 invisible(self),这样我们就可以对这个方法进行链式调用了,例如

> tom$incSalary(0.1)$incSalary(0.2)$incSalary(0.3)
change tom's salary!
change tom's salary!
change tom's salary!
> tom
<Person>
  Public:
    clone: function (deep = FALSE) 
    incSalary: function (percent) 
    initialize: function (name, money) 
    name: tom
    say: function () 
    test: function () 
  Private:
    money: 1887.6
    setMoney: function (m) 

注意:我们在访问成员时都是使用了 selfprivate 对象,而不管是在 public 参数里面还是 private 参数里面

我们可以测试一下 selfprivate 到底是什么,我们在上面的例子中,添加一个 test 公有函数

Person <- R6Class(
  "Person",
  public = list(
    name = NA,
    initialize = function(name, money) {
      self$name <- name
      private$money <- money
    },
    say = function() {
      cat("my name is ", self$name)
    },
    incSalary = function(percent) {
      private$setMoney(private$money * (1 + percent))
    },
    test = function() {
      print(self)
      print(strrep("=", 20))
      print(private)
      print(strrep("=", 20))
      print(ls(envir = private))
    }
  ),
  private = list(
    money = NA,
    setMoney = function(m) {
      cat(paste0("change ", self$name, "'s salary!"))
      private$money <- m
    }
  )
)

测试一下

> tom <- Person$new(name = "tom", 1000)
> tom$test()
<Person>
  Public:
    clone: function (deep = FALSE) 
    incSalary: function (percent) 
    initialize: function (name, money) 
    name: tom
    say: function () 
    test: function () 
  Private:
    money: 1000
    setMoney: function (m) 
[1] "===================="
<environment: 0x7fe1d2135cf0>
[1] "===================="
[1] "money"    "setMoney"

从上面的输出结果可以看出,self 对象更像是实例化的对象本身,而 private 则是一个环境空间。这个环境空间就像是变量的作用域,因此,private 只在类中被调用,而对于类外部是不可见的。

4.4 主动绑定

主动绑定可以让对函数调用看起来像是在访问属性,主动绑定总是公开成员,外部可见的。

这与 Python 中的 @property 装饰器是一样的,有些时候,我们并不想直接把数据属性暴露在外面,被随意修改。

例如,我们有一个 Student 类,包含一个 score 属性,但是不想将其暴露在外面被随意修改,所以我们将其设置为私有属性,同时定义 get/set 方法,并在 set 方法中控制有效范围

Student <- R6Class(
  "Student",
  public = list(
    name = NA,
    initialize = function(name) {
      self$name <- name
    },
    getScore = function() {
      return(private$score)
    },
    setScore = function(score) {
      if (score < 0 || score > 100) 
        stop("Score incorrect!")
      private$score <- score
    }
  ),
  private = list(
    score = NA
  )
)

使用

> sam <- Student$new("sam")
> sam$setScore(99)
> sam$getScore()
[1] 99
> sam$setScore(101)
Error in sam$setScore(101) : Score incorrect!

这样是可以达到我们的目的,但还是不能像属性那样用起来方便

所以 R6 为我们提供了 active 参数,重新改写上面的例子

Student <- R6Class(
  "Student",
  public = list(
    name = NA,
    initialize = function(name) {
      self$name <- name
    }
  ),
  private = list(
    .score = NA
  ),
  active = list(
    score = function(s) {
      if (missing(s))
        return(private$.score)
      if (s < 0 || s > 100)
        stop("Score incorrect!")
      private$.score <- s
    }
  )
)

注意public, privateactive 参数内的属性名必须唯一,所以我们将私有属性改为了 .score

> sam <- Student$new("sam")
> sam
<Student>
  Public:
    clone: function (deep = FALSE) 
    initialize: function (name) 
    name: sam
    score: active binding
  Private:
    .score: NA
> sam$score
[1] NA
> sam$score <- 100
> sam$score
[1] 100

4.5 继承

R6 通过 inherit 参数指定父类,例如,我们定义一个 worker 类,它继承自上面的 Person

Worker <- R6Class(
  "Worker",
  inherit = Person,
  public = list(
    company = "Gene",
    info = function() {
      print("NGS analysis!")
    }
  )
)

创建对象并使用父类的方法

> siri <- Worker$new("Siri", 100)
> siri$incSalary(0.1)
change Siri's salary!
> siri
<Worker>
  Inherits from: <Person>
  Public:
    clone: function (deep = FALSE) 
    company: Gene
    incSalary: function (percent) 
    info: function () 
    initialize: function (name, money) 
    name: Siri
    say: function () 
    test: function () 
  Private:
    money: 110
    setMoney: function (m) 

我们可以使用 super 对象来调用父类的方法,让我们来重写 incSalary 方法

Worker <- R6Class(
  "Worker",
  inherit = Person,
  public = list(
    company = "Gene",
    info = function() {
      print("NGS analysis!")
    },
    incSalary = function(percent) {
      super$incSalary(percent + 0.1)
    }
  )
)

运行与上面相同的代码

> siri <- Worker$new("Siri", 100)
> siri$incSalary(0.1)
change Siri's salary!
> siri
<Worker>
  Inherits from: <Person>
  Public:
    clone: function (deep = FALSE) 
    company: Gene
    incSalary: function (percent) 
    info: function () 
    initialize: function (name, money) 
    name: Siri
    say: function () 
    test: function () 
  Private:
    money: 120
    setMoney: function (m) 

可以看到,工资的增长增加了 0.1

4.6 引用对象字段

如果您的 R6 类的属性中包含其他类的实例化对象时,该对象将在 R6 对象的所有实例中共享。例如

ShareClass <- R6Class(
  "ShareClass",
  public = list(
    num = NULL
  )
)

Common <- R6Class(
  "Common",
   public = list(
     share = ShareClass$new()
  )
)
> c1 <- Common$new()
> c1$share$num <- 1

> c2 <- Common$new()
> c2$share$num <- 2

> c1$share$num
[1] 2

注意:不能把实例化对象放在 initialize 方法中

UnCommon <- R6Class(
  "Common",
  public = list(
    share = NULL,
    initialize = function() {
      share <<- ShareClass$new()
    }
  )
)
n1 <- UnCommon$new()
n1$share$num <- 1

n2 <- UnCommon$new()
n2$share$num <- 2

n1$share$num

可以看到,share 属性并没有改变

4.7 可移植和不可移植类

portable 参数可以设置 R6 类是否为可移植类型还是不可移植类型,主要区别在于:

4.8 为现有类添加成员

有时候,我们需要对已经创建的类添加新的成员,可以使用 $set() 方法来完成。

例如,我们为 Student 类添加一个属性

> Student$set("public", "age", 21)
> sam <- Student$new("sam")
> sam
<Student>
  Public:
    age: 21
    clone: function (deep = FALSE) 
    initialize: function (name) 
    name: sam
    score: active binding
  Private:
    .score: NA

当然也可以使用这种方式修改属性值

> Student$set("public", "age", 18, overwrite = TRUE)
> sam <- Student$new("sam")
> sam
<Student>
  Public:
    age: 18
    clone: function (deep = FALSE) 
    initialize: function (name) 
    name: sam
    score: active binding
  Private:
    .score: NA

注意:我们设置了 overwrite=TRUE

添加一个方法

> Student$set("public", "getName", function() self$name)
> sam <- Student$new("sam")
> sam$getName()
[1] "sam"

4.9 打印对象

R6 对象有一个默认的 print 方法,列出对象的所有成员。我们可以为类自定义一个 print 方法,那么它将覆盖默认的方法

Student <- R6Class(
  "Student",
  public = list(
    name = NA,
    initialize = function(name) {
      self$name <- name
    },
    print = function(...) {
      cat("class", class(self), "\n")
      cat(ls(self), sep = ',')
    }
  ),
  private = list(
    .score = NA
  ),
  active = list(
    score = function(s) {
      if (missing(s))
        return(private$.score)
      if (s < 0 || s > 100)
        stop("Score incorrect!")
      private$.score <- s
    }
  )
)
> sam <- Student$new("sam")
> print(sam)
class Student R6 
clone,initialize,name,print,score
上一篇下一篇

猜你喜欢

热点阅读