advanced R - 面向对象

2022-08-14


一切皆是对象。 一切皆是函数调用。 以上所谓的对象时指R中所有的数据,包括函数都可以作为对象进行输入,下面所讨论的才是面向对象编程中的对象。

R中有四种面向对象的结构,分别为S3,S4(R语言的前身为S语言),RC(Reference Class,也称为R5),以及R6。其中前三种为R内置,R6由R6包提供。

当我们讨论面向对象时,我们在讨论什么?首先我们具有一个类(class)的概念,这是一个抽象的集合,犹如当我们在讨论人类时。一个类可以拥有一个子类,犹如当我们讨论黄种人,白种人,黑种人之与人类,反之,人类之与黄种人即为父类。父类与子类之间具有继承关系。一个类如何决定它是该类?这是一个哲学问题,正如我如何定义我。在R中一个类的定义包括属性(filed),方法(methods),其中属性在语言上是一个键值对(key-value pair)的形式,为一个名词性的描述,而方法是动词性的,体现了该类具有的操作。当我们议论某雪,某爱豆时,这就是我们所谓的对象(object),该对象是由某类实例化而来(initialize),为一个具体确定的个体。对于类的属性以及方法的修改为modified-in-place,即对其本身的修改,而不是modified-in-copy(复制对象后修改,重新建立name和对象的指向)。当我们讨论赋值 <-时,即是将一个name指向了一个对象,一个对象(针对RC,以及R6)被修改以后,所有指向它的name的值同时发生变化,即许多name共享一个对象,而该对象的修改是modified-in-place,那么通过任何一个指向该对象的name对该对象进行修改,其它指向该对象的name的值也同时发生变化。

以上所讨论的面向对象在RC,以及R6中体现。S3,以及S4中的面向对象是通过泛型函数(generic)实现的。所谓泛型函数,即在一个函数名例如plot下,包括了多个函数Plot.xxxx, plot.yyy, plot.zzz,当我们调用plot函数时,该plot泛函自动识别输入对象的类型从而调用不同的Plot.*** 函数。该面向对象的实现方式,属性和方法进行了分离,在对象中仅定义了属性,而方法通过泛函与对象进行匹配。另外,只有S4对属性的调用使用符号 @。

以下所有的内容来自R语言面向对象编程 教程

第一篇 基于S3的面向对象

  1. S3用起来简单,但在实际的面向对象编程的过程中,当对象关系有一定的复杂度,S3对象所表达的意义就变得不太清楚。
  2. S3封装的内部函数,可以绕过泛型函数的检查,以直接被调用。
  3. S3参数的class属性,可以被任意设置,没有预处理的检。
  4. S3参数,只能通过调用class属性进行函数调用,其他属性则不会被class()函数执行。
  5. S3参数的class属性有多个值时,调用时会被按照程序赋值顺序来调用第一个合法的函数。
第一节 创建S3对象
library(pryr)  #辅助工具,用于检查对象类型
#通过变量创建S3对象
x <- 1
attr(x,'class') <- 'foo'
x

attr(x,"class")
class(x)

# 用pryr包的otype函数,检查x的类型
otype(x)

# 通过structure()函数创建S3对象
y <- structure(2,class="foo")
y
attr(y,"class")
class(y)
otype(y)

# 创建一个多类型的S3对象
x <- 1
attr(x,"class") <- c("foo","bar")
class(x)
otype(x)
第二节 泛型函数和方法调用
# 用UseMethod()定义teacher泛型函数
teacher <- function(x,...) UseMethod("teacher")
# 用pryr包中ftype()函数,检查teacher类型
ftype(teacher)
[1] "s3" "generic"

# 定义teacher内部函数
teacher.lecture <- function(x,...) print("讲课")
teacher.assignment <- function(x,...) print("布置作业")
teacher.correcting <- function(x,...) print("批改作业")
teacher.default <- function(x,...) print("你不是teacher")

a <- "teacher"
# 给老师变量设置行为
attr(a,"class") <- 'lecture'
# 执行老师的行为
teacher(a)
[1] “讲课”

# 直接调用teacher中定义的行为,虽然没啥用。
teacher.lecture()

第三节 查看S3对象的函数

# 查看teacher对象
teacher
function(x,...) Usemethod("teacher")

# 查看teacher对象的内部函数
> methods(teacher)
[1] teacher.assignment teacher.correcting teacher.default teacher.lecture

#通过methods()的generic.function参数,来匹配泛型函数名字
> methods(generic.function = predict)
[1] predict.ar* ......

# 通过methods()的class参数,来匹配类的名字
methods(class=lm)
[1]add1.lm* ......

# 用getAnywhere()函数,查看所有函数
getAnywhere(teacher.lecture)

# 使用getS3method()函数,也同样可以查看不可见的函数
getS3method("predict","ppr")

第四节 s3 对象的继承关系

node <- function(x) UseMethod("node",x)
> node.default <- function(x) "Default node"

#father函数
> node.father <- function(x) c("father")

# son函数,通过NextMethod()函数只想father函数
> node.son <- function(x) c('son',NextMethod())

#定义n1
> n1 <- structure(1,class=c("father"))
# 在node函数中传入n1,执行node.father()函数
> node(n1)
[1] "father"

# 定义n2,设置class属性为两个
> n2 <- structure(1,class=c("son","father"))
# 在node函数中传入n2,执行node.son()函数和node.father()函数
> node(n2)
[1] "son" "father"

第二篇 基于S4的面向对象

第一节 创建S4对象

# 加载pryr包
library(pryr)

# 基类Shape
setClass("Shape",slots=list(name="character",shape="character"))

# Ellipse继承Shape
setClass("Ellipse",contains = "Shape",slots=list(radius="numeric"),prototype = list(radius=c(1,1),shape="Ellipse"))
# 验证radius参数
setValidity("Ellipse",function(object){
  if(length(object@radius)!=2) stop("It's note Ellipse")
  if(length(which(object@radius<=0))>0) stop("Radius is negative")
})

# Circle继承Ellipse
setClass("Circle",contains = "Ellipse",slots=list(radius="numeric"),prototype=list(radius=1,shape="Circle"))
# 验正radius属性值要大于等于0
setValidity("Circle",function(object){
  if(object@radius <= 0) stop("Radius is negative")
})

# 定义area接口
setGeneric("area",function(obj,...) standardGeneric("area"))

# 定义area的Ellipse实现
setMethod("area","Ellipse",function(obj,...){
  cat("Ellipse Area: \n")
  pi*prod(obj@radius)
})

# 定义area的Circle实现
setMethod("area","Circle",function(obj,...){
  cat("Circle Area:\n")
  pi*obj@radius^2
})

# 创建实例
e1 <- new("Ellipse",name="e1",radius=c(2,5))
e2 <- new("Circle",name="e2",radius=2)

# 计算面积
area(e1)
area(e2)

第二节 从一个已经实例化的对象中创建新对象

setClass("Person",slots=list(name="character",age="numeric"))

# 创建一个对象实例n1
n1 <- new("Person",name="n1",age=19)
n1

# 从实例n1中,创建实例n2,并修改name的属性值
n2 <- initialize(n1,name="n2")
n2

第三节 访问对象属性

setClass("Person",slots=list(name="character",age="numeric"))
a <- new("Person",name="a")

# 访问S4对象的属性
a@name
## [1] "a"
slot(a,"name")
## [1] "a"
# 错误的访问
#a$name
#a[1]

第四节 查看S4对象的函数

library(pryr)
# 检查work的类型
ftype(work)

# 直接查看work函数
work

# 查看work函数的显示定义
showMethod(work)

# 查看Person对象的work函数现实
getMethod("work","Person")

# 检查Person对象有没有work函数
existMethod("work","Person")
hasMethod("work","Person")

第三篇 基于RC的面向对象

第一节 创建RC对象

# 创建Animal类,包括name属性,构造方法initialize(),叫声方法bark()
Animal <- setRefClass("Animal",
                      fields=list(name="character"),
                      methods=list(
                        initialize = function(name){
                          name <<- "Animal"
                        },
                        bark = function()print("Animal::bark")
                      ))

# 创建Cat类,继承Animal类,并重写(overwrite
#)了initialize()和bark()
Cat <- setRefClass("Cat",contains="Animal",
                   methods=list(
                     initialize = function(name) name <<- 'cat',
                     bark = function() print(paste(name,"is miao miao"))
                   ))

# 创建Dog类
Dog <- setRefClass("Dog",contains="Animal",
                   methods=list(
                     initialize = function(name) name <<- 'Dog',
                     bark = function() print(paste(name,"is wang wang"))
                   ))

# 创建Duck类
Duck<- setRefClass("Duck",contains="Animal",
                   methods=list(
                     initialize = function(name) name <<- 'Duck',
                     bark = function() print(paste(name,"is ga ga"))
                   ))


# 创建cat实例
cat <- Cat$new()
cat$name

# cat叫声
cat$bark()

第二节 RC对象实例化后的内置方法,属性,以及辅助函数

  1. 内置方法
  1. 内置属性
  1. 辅助函数

第四篇 基于R6的面向对象

第一节 创建R6类

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

第二节 R6的主动绑定

主动绑定(Active binding)是R6中一种特殊的函数调用方式,把对函数的访问表现为对属性的访问,主动绑定属于公有成员。在类的定义中,通过设置activate参数实现主动绑定的功能,给Person类增加两个主动绑定的函数activate和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 # 查看公有属性
conan$active #调用主动绑定的active()函数,结果为num +10 = 100+10 
# 给主动绑定额active函数传参书,用赋值符号"<-",而不是方法调用"()"
conan$active <- 20
conan$num

第三节 R6的继承关系

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 <- R6Class("Worker",
                   inherit = Person, #继承,指向父类
                   public = list(
                     bye = function(){
                       print(paste("bye",self$name))
                     }
                   )
                   )

u1 <- Person$new("Conan","Male") #实例化父类
u1$hello()

u2 <- Worker$new("Conan","Male") # 实例化子类
u2$hello()
u2$bye()

# 重写父类方法
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))
                    }
                  ))

u2 <- Worker$new("Conan","Male")
u2$hello() # 调用hello()方法

# 子类中调用父类的方法
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()

第四节

R6类对象的静态属性

A <- R6Class("A",
             public=list(
               x = NULL
             ))

B <- R6Class("B",
             public=list(
               a = A$new()
             ))

b <- B$new() # 实例化B对象

b$a$x <- 1 # 给x变量赋值
b$a$x

## [1] 1
b2 <- B$new()
b2$a$x <-2
b2$a$x
## [1] 2
b$a$x
## [1] 2

第五节 R6类的可移植类型

RC <- setRefClass("RC",
                  fields = list(x="numeric"),
                  methods = list(
                    getx = function() x,
                    setx = function(value) x <<- value
                  ))

rc <- RC$new()
rc$setx(10)
rc$getx()

## [1] 10

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

PR6 <- R6Class("PR6",
               portable = TRUE,
               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的动态绑定

A <- R6Class("A",
             public = list(
               x = 1,
               getx = function() x
             ))

A$set("public","getx2",function() self$x*2) # 动态增加getx2()方法

s <- A$new()
s$getx2()
## [1] 2

A$set("public","x",10,overwrite=TRUE) # 动态改变x属性

s <- A$new()
s$x
## [1] 10
s$getx2()
## [1] 20

第七节 R6的打印函数

A <- R6Class("A",
             public = list(
               x = 1,
               getx = function() self$x
             ))

a <- A$new()
print(a) #使用默认的打印方法

## <A>
##   Public:
##     clone: function (deep = FALSE) 
##     getx: function () 
##     x: 1

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 publicclonegetxprintx:clone,getx,print,x

第八节 R6实例化对象的存储

# 类中定义的属性和方法统一存储到一个S3对象中
A <- R6Class("A",
             class=TRUE,
             public=list(
               x = 1,
               getx = function() self$x
             ))

a <- A$new()
class(a)

# 把类中定义的属性和方法统一存储到一个单独的环境空间中。
B <- R6Class("B",
             class=TRUE,
             public=list(
               x = 1,
               getx = function() self$x
             ))

b <- B$new()
class(b)

A <- R6Class("A",
             lock = TRUE,# 锁定环境空间
             public= list(
             x = 1
             ))
## R6Class A: 'lock' argument has been renamed to 'lock_objects' as of version 2.1.This code will continue to work, but the 'lock' option will be removed in a later version of R6
s <- A$new()
ls(s)
## [1] "clone" "x"
# s$aa <- 11 # 增加新变量 Error
# rm("x",envir=s) # Error