美文网首页
R 面向对象编程(四)—— R6

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

作者: 名本无名 | 来源:发表于2021-03-23 22:18 被阅读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 类是否为可移植类型还是不可移植类型,主要区别在于:

    • 可移植类支持跨包继承,但是不可移植类型的兼容性不好
    • 可移植类使用 selfprivate 来访问成员。不可移植类直接使用属性名称来访问,如 share,并使用 <<- 操作符对这些成员进行赋值

    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
    

    相关文章

      网友评论

          本文标题:R 面向对象编程(四)—— R6

          本文链接:https://www.haomeiwen.com/subject/qgpqhltx.html