专注系列化、高质量的R语言教程

本号已支持快捷转载,无需白名单即可转载)


本系列将介绍R语言中三个与面向对象的编程(Object-Oriented Programming,OOP)相关的工具包:protoR6和基础包methods。这是一个承上启下的系列,上承《自定义ggplot2绘图系统函数》系列,下启《基于mlr3工具包的机器学习》系列(该系列将在本系列之后推出)。这两个系列的编程风格都属于OOP,前者基于proto包的proto对象,后者基于R6包的R6类。

本篇介绍R6工具包,主要参考资料[1]
[https://r6.r-lib.org/articles/Introduction.html]。

  • 1 引言

  • 2 创建R6类和对象

    • 2.1 创建类

    • 2.2 创建对象

  • 3 私有属性和方法

  • 4 类的继承

  • 5 外部引用

  • 6 新增类的元素

  • 7 类的锁定和解锁

  • 8 克隆对象

1 引言

proto基于对象的编程不同,R6采用的是基于类的编程(class-based programming)。类(class)和对象(object)的关系是:对象是类的实例,类是对象的模板;定义对象之前需要先定义类[2]

R语言基础包中有S3类、S4类和reference class(R5类为其非正式称呼),R6是基于这个顺序命名的。

2 创建R6类和对象

2.1 创建类

创建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()
)

这里先看前两个参数:

  • classname:类的名称;

  • public:类的公用属性和方法。

由“类”到“对象”,需要使用基础包methods中的new()函数,它要求“类”里必须包含initialize方法。

如下是一个示例:

library(R6)
Person <- R6Class(classname = "Person",public = list(## 属性name = NULL,age = NULL,## 方法intro = function() {print(paste("Hello, I am", self$name, "and", self$age, "years old"))},initialize = function(name, age) {self$name <- nameself$age <- agereturn(self$intro())})
)

在上面的代码中,我们只使用了classnamepublic两个参数。前者无需多说。在public中,我们定义了两个属性:nameage;两个方法:introinitialize

要点如下:

  • public的数据结构形式为列表,元素(属性或方法)之间使用逗号隔开;

  • 可以使用self表示类本身,进而在方法中引用它的的属性和方法。

R6类的数据类型:

class(Person)
## [1] "R6ClassGenerator"

2.2 创建对象

从R6类创建对象的代码形式为object <- class$new(...),其中new()函数来自基础包methods,它的参数为类的initialize方法的参数。

根据类Person创建对象:

Tom <- Person$new("Tom", 20)
## [1] "Hello, I am Tom and 20 years old"Jane <- Person$new("Jane", 18)
## [1] "Hello, I am Jane and 18 years old"  Tom$age <- 30
Tom$intro()
## [1] "Hello, I am Tom and 30 years old"

3 私有属性和方法

public参数外,还可以使用R6Class()函数的private参数定义私有属性和方法。

要点如下:

  • private的数据结构形式为列表,元素(属性或方法)之间使用逗号隔开;

  • 私有属性和方法可以使用private$x的形式在其他方法中进行引用。

如下代码,我们把intro方法放到private参数中:

Person2 <- R6Class(classname = "Person2",public = list(name = NULL,age = NULL,initialize = function(name, age) {self$name <- nameself$age <- agereturn(private$intro())}), ## 私有属性和方法private = list(intro = function() {print(paste("Hello, I am", self$name, "and", self$age, "years old"))} )
)

根据类Person2创建对象:

Tom2 <- Person2$new("Tom", 20)
## [1] "Hello, I am Tom and 20 years old"

属性和方法的私有部分与公用部分的区别是:前者不能使用object$x的形式进行访问。如下代码会报错:

Tom2$intro()
## Error: attempt to apply non-function

在RStudio中,当我们在对象后输入$时,自动联想中也不会出现私有部分,因此可以将一些作为过程的属性和方法放到私有部分。如下对比了TomTom2对象:

4 类的继承

我们可以在一个类的基础上定义一个新类,这称为继承(inheritance);前者是后者的父类(super-class),后者是前者的子类(sub-class)。

子类除了继承或修改父类的属性和方法外,还可以定义新的属性和方法。下面代码中,我们在Person2的基础上修改intro方法形成Person3

library(lubridate)
Person3 <- R6Class(## 继承inherit = Person2,private = list(## 修改intro方法intro = function() {born = year(Sys.Date()) - self$ageprint(paste("Hello, I am", self$name, "and borned in", born))} )
)Tom3 <- Person3$new("Tom", 20)
## [1] "Hello, I am Tom and borned in 2002"

在子类中可以以super$method(...)的形式调用父类的方法:

Person4 <- R6Class(inherit = Person2,public = list(initialize = function(name, age) {self$name <- nameself$age <- agereturn(super$intro())}),private = list(intro = function() {born = year(Sys.Date()) - self$ageprint(paste("Hello, I am", self$name, "and borned in", born))} )
)Tom4 <- Person4$new("Tom", 20)
## [1] "Hello, I am Tom and 20 years old"

注意:调用的只是父类中方法的形式,参数值仍来自子类。

5 外部引用

如果把其他类的对象作为要定义类的属性,通常情况下应将赋值过程放在initialize方法中,否则该类的所有对象会共享这一属性值。

下面例子中,我们使用类SimpleClass的对象作为类Person5age属性。

不放在initialize方法中:

SimpleClass <- R6Class("SimpleClass",public = list(x = NULL)
)Person5 <- R6Class(classname = "Person5",public = list(name = NULL,## 外部引用age = SimpleClass$new(),intro = function() {print(paste("Hello, I am", self$name, "and", self$age$x, "years old"))},initialize = function(name) {self$name <- namereturn(self$intro())})
)

如下根据Person5创建两个对象,因为他们会共享age属性,所以年龄始终保持一致:

Tom5 <- Person5$new("Tom")
Jane5 <- Person5$new("Jane") Tom5$age$x <- 20
Tom5$intro()
## [1] "Hello, I am Tom and 20 years old"
Jane5$intro()
## [1] "Hello, I am Jane and 20 years old"Tom5$age$x <- 30
Tom5$intro()
## [1] "Hello, I am Tom and 30 years old"
Jane5$intro()
## [1] "Hello, I am Jane and 30 years old"

放在initialize方法中:

Person6 <- R6Class(classname = "Person6",public = list(name = NULL,age = NULL,intro = function() {print(paste("Hello, I am", self$name, "and", self$age$x, "years old"))},initialize = function(name) {self$name <- name## 在initialize方法内进行外部引用self$age <- SimpleClass$new()return(self$intro())})
)Tom6<- Person6$new("Tom")
Jane6 <- Person6$new("Jane")
Tom6$age$x <- 20
Jane6$age$x <- 30Tom6$intro()
## [1] "Hello, I am Tom and 20 years old"
Jane6$intro()
## [1] "Hello, I am Jane and 30 years old"

6 新增类的元素

在类已被创建后,可以使用set()函数增加属性和方法。

如下,先生成类Person7,再在它的基础上新增一个公用方法born

Person7 <- R6Class(inherit = Person)
Person7$set("public", "born", function() year(Sys.Date()) - self$age)  Tom7 <- Person7$new("Tom", 20)
Tom7$born()
## [1] 2002

7 类的锁定和解锁

如果希望在类创建后,不允许对其进行修改,可以在创建时设置参数lock_class = TRUE。如下,此时再想新增元素就会报错:

Person8 <- R6Class(inherit = Person,lock_class = T
)Person8$set("public", "born", function() year(Sys.Date()) - self$age)
## Error in Person7$set("public", "born2", function() year(Sys.Date()) - :
## Can't modify a locked R6 class.

锁定类后,可以使用unlock()函数对其进行解锁:

Person9 <- R6Class(inherit = Person,lock_class = T
)
## 解锁
Person9$unlock()Person9$set("public", "born", function() year(Sys.Date()) - self$age)
Tom9 <- Person9$new("Tom", 20)
Tom9$born()
## [1] 2002

8 克隆对象

这一部分对应proto对象的备份(详见proto对象)。在那篇推文中,我们已经知道使用赋值符号不能克隆环境对象,而只是给对象起了一个新名字:

Tom10 <- Tom
Tom$age <- 20
Tom10$intro()
## [1] "Hello, I am Tom and 20 years old"Tom$age <- 30
Tom10$intro()
## [1] "Hello, I am Tom and 30 years old"

从本例可以看出,Tom10的内容会随着Tom的内容变化而变化;本质上,TomTom10是同一对象的两个名字。

克隆R6对象可以使用clone()函数:

Tom11 <- Tom$clone()
Tom11$age <- 40
Tom11$intro()
## [1] "Hello, I am Tom and 40 years old"Tom$intro()
## [1] "Hello, I am Tom and 30 years old"

如果想不允许对象被克隆,在创建类时可以设置参数cloneable = FALSE 。如果类中包含有外部引用,克隆时应设置参数deep = TRUE

## 未设置参数deep = T
Tom12 <- Tom6$clone()
Tom6$intro()
## [1] "Hello, I am Tom and 20 years old"
Tom12$age$x <- 20
Tom12$intro()
## [1] "Hello, I am Tom and 20 years old"
Tom6$intro()
## [1] "Hello, I am Tom and 20 years old"## 设置参数deep = T
Tom13 <- Tom6$clone(deep = T)
Tom6$intro()
## [1] "Hello, I am Tom and 20 years old"
Tom13$age$x <- 30
Tom13$intro()
## [1] "Hello, I am Tom and 30 years old"
Tom6$intro()
## [1] "Hello, I am Tom and 20 years old"

更复杂的情况请参见参考资料[1]或“阅读原文”。

参考资料

[1]

Introduction to R6: https://r6.r-lib.org/articles/Introduction.html

[2]

类和对象: https://baike.baidu.com/item/%E7%B1%BB%E5%92%8C%E5%AF%B9%E8%B1%A1/1394902

R语言与面向对象的编程(3):R6类相关推荐

  1. 【C++ 语言】面向对象 ( 模板编程 | 函数模板 | 类模板 )

    文章目录 函数模板 类模板 代码示例 函数模板 1. 模板编程 : 类似于 Java 中的泛型编程 ; ① 函数模板 : 对应着 Java 中的泛型方法 ; ② 类模板 : 对应 Java 中的泛型类 ...

  2. R语言条件判断语句编程:使用if/else语句实现条件逻辑判断、使用all函数判断向量中的值是否全部都满足条件

    R语言条件判断语句编程:使用if/else语句实现条件逻辑判断.使用all函数判断向量中的值是否全部都满足条件 目录

  3. C语言实现面向对象风格编程

    C语言实现面向对象风格编程 在C语言中实现面向对象编程的一种常见方法是通过结构体和函数指针来模拟类和对象.下面是一个简单的示例,演示如何使用C语言实现一个简单的面向对象程序. 首先,定义一个结构体,表 ...

  4. R语言条件判断语句编程:使用嵌套(一串)if/else语句实现条件逻辑判断、并对指定变量进行条件赋值

    R语言条件判断语句编程:使用嵌套(一串)if/else语句实现条件逻辑判断.并对指定变量进行条件赋值 目录

  5. R语言for循环语句编程:for循环遍历向量内容并修改向量内容

    R语言for循环语句编程:for循环遍历向量内容并修改向量内容 目录 R语言for循环语句编程:for循环遍历向量内容并修改向量内容

  6. 如何在R语言中进行中文编程

    又发现了个好玩的hiahiahia: 给四个例子,大家看了例子应该都能会用中文写r语言: 例1 预定义: # 定义函数 "生成正态分布随机数"<-function(数量=1, ...

  7. 【数据分析R语言系列】R语言函数与函数式编程、作用域和apply 家族

    文章目录 函数与函数式编程 创建和使用函数 作用域 任意参数 函数式编程 传入和返回函数 apply 家族 apply lapply.sapply 和 vapply 函数与函数式编程 函数是代码模板. ...

  8. 面向对象:编程范式、类、对象

    编程范式: 1. 面向过程编程: 核心是"过程","过程"指的是解决问题的步骤:就相当于在设计一条流水线 优点:复杂问题流程化,进而简单化 缺点:可扩展性差,前 ...

  9. c++面向对象高级编程 学习十一 类模板、函数模板、成员模板

    namespace经验谈: 团队中函数或类的名字可能会冲突,因此使用namespace进行区分. 类模板: template<typename T> 函数模板: template<c ...

最新文章

  1. LeetCode 206 Reverse Linked List--反转链表--迭代与递归解法--递归使用一个临时变量,迭代使用3个
  2. Apache Shiro Architecture--官方文档
  3. python区块链开发_Fabric区块链Python开发详解
  4. Android ConstraintLayout ConstraintSet动态布局
  5. 配置DispatcherServlet
  6. windows系统上openssh client的离线安装
  7. 2021中考高考成绩查询,2021中考
  8. 不要以为学java,.net或VB的就很牛
  9. mysql 集群 qps_MySQL Cluster:如何通过扩展为MySQL带来2亿QPS
  10. PhpStudy升级数据库到mysql5.7方法
  11. 5.2 - Function Basics
  12. 一支口红用了5年_用了7年微信才知道!原来微信隐藏5大实用功能,比app更好用...
  13. 决策树中的过拟合问题
  14. linux虚拟机模板部署模板,创建和部署基于 Linux 的虚拟机模板
  15. 2008是中国的奥运年
  16. EF批量添加数据BulkInsert
  17. 2008-2019年高等学校科技统计资料汇编
  18. mybatis自定义枚举类型的转换器以及各种使用场景
  19. C4D模型工具—反转法线
  20. JS的Date函数汇总

热门文章

  1. Django框架---- 信号
  2. 【Leetcode】[7]Reverse Integer 反转整数
  3. java爬取网页数据_利用Python做数据分析—对前程无忧数据类岗位进行分析
  4. 服务器分辨率无法修改器,大神讲解win10分辨率无法修改的具体解决手法
  5. win10 搜索功能失效
  6. 【Python】发送微信公众号消息(附完整代码)一一CSDN21天学习挑战赛
  7. 软考中级-嵌入式系统设计师(一)
  8. LinuxI/O多路复用转接服务器——select模型实现
  9. 测试工作方法与思想分享之
  10. 外贸SOHO有哪些方法提高工作效率?