参见:《R 的极客理想 高级开发篇》

《R inferno》

R 语言提供了三种底层对象系统,分别是:S3 类型、S4 类型,RC 类型。

  • S3 对象简单,具有动态性,但结构化特征不明显;
  • S4 对象结构化,功能强大;
  • RC 对象用于解决S3、S4 对象难以解决的对象设计。

需要使用的包为:

  1. install.packages(pacman)
  2. pacman::p_load(pryr)

S3 对象

在R 中,我们可以通过attr 来修改对象属性,创建S3 对象的最简单方法,就是修改变量的class 属性为一个自定义值:

  1. > x <- 1
  2. > class(x)
  3. [1] "numeric"
  4. > x
  5. [1] 1
  6. attr(,"class")
  7. [1] "random"
  8. > class(x)
  9. [1] "random"

我们可以通过pryr 包中的函数otype 查看x 类型:

  1. > y <- 1:10
  2. > otype(y)
  3. [1] "base"
  4. > otype(x)
  5. [1] "S3"

S3没有正式的类型间关系的定义,一个对象可以有多个类型,表现为其class属性是一个向量:

  1. > class(x) <- c("x1","x2")
  2. > x
  3. [1] 1
  4. attr(,"class")
  5. [1] "x1" "x2"

方法调用

方法分派是指由泛型函数 (generic function) 来决定对某个对象使用的方法. 所有泛型函数都有类似的形式: 一个广义的函数名, 并调用 UseMethod() 来决定为对象分派哪个方法. 这也使得泛型函数的形式都很简单, 比如 mean() :

  1. function (x, ...)
  2. UseMethod("mean")
  3. <bytecode: 0x103792920>
  4. <environment: namespace:base>

首先通过UseMethod 定义类型,接着会根据对象的 class 属性来决定分派什么方法,所以方法必须以 generic.class 的方式命名才能被 UseMethod() 找到, 比如:

  1. mean.numeric <- function(x, ...) sum(x)/length(x)
  2. mean.data.frame <- function(x, ...) sapply(x, mean, ...)
  3. mean.matrix <- function(x, ...) apply(x, 2, mean)

比如class 为numeric 的对象在使用mean 方法是,调用的就是第一个方法。

下面为例子:

  1. test <- function(x, ...) UseMethod("test")
  2. test.a1 <- function(x, ...) "a1"
  3. test.a2 <- function(x, ...) "a2"
  4. b <- structure(1, class = "a1") # a1 类型,找的是a1 方法
  5. test(b)
  6. c <- structure(1, class = "a3")
  7. test(c) # 没有a3 类型对应的函数,会报错
  8. # Error in UseMethod("test") : "test"没有适用于"a3"目标对象的方法

我们也可以直接调用方法:

  1. > test.a2(c)
  2. [1] "a2"
  3. # 强制执行,并不会报错

我们还可以通过default 设置默认的方法:

  1. > test.default <- function(x, ...) "default"
  2. > test(c)
  3. [1] "default"

如果 class 属性是一个向量 c(”foo”, ”bar”), 则优先寻找mean.foo, 然后 mean.bar, 最后 mean.default。

因此S3 对象使用起来非常的方便,用户并不需要确定具体的对象类型,只需要将对象放到函数中,就会自动寻找相关的类型,并选择相关函数计算。

查看函数及继承

因为S3 对象创建的方法是泛型的generic function,因此如果直接查看:

  1. > test
  2. function(x, ...) UseMethod("test")

我们可以使用methods 函数查看S3 对象函数的全部泛型函数(在当前加载的R 空间内):

  1. > methods(test)
  2. [1] test.a1 test.a2 test.default
  3. see '?methods' for accessing help and source code

接着就可以直接使用函数或用getS3method 获得其源代码:

  1. > getS3method("test", "a1")
  2. function(x, ...) "a1"

或者指定class 类,获得所有S3对象类有关的函数:

  1. > methods(class=lm)
  2. [1] add1 alias anova case.names
  3. [5] coerce confint cooks.distance deviance
  4. [9] dfbeta dfbetas drop1 dummy.coef
  5. [13] effects extractAIC family formula
  6. [17] hatvalues influence initialize kappa
  7. [21] labels logLik model.frame model.matrix
  8. [25] nobs plot predict print
  9. [29] proj qr residuals rstandard
  10. [33] rstudent show simulate slotsFromS3
  11. [37] summary variable.names vcov
  12. see '?methods' for accessing help and source code

因此实际上我们在对lm 类型对象xx 使用如summary(xx) 时,实际上使用的就是函数summary.lm(xx)。

由于 class 属性可以是向量, 所以 S3 中的继承关系自然地表现为 class 属性的前一个分量是后一个的子类.NextMethod() 函数可以使得一系列的方法被依次应用于对象上.

  1. bar <- function(x) UseMethod("bar", x)
  2. bar.son <- function(x) c("I am son.", NextMethod())
  3. bar.father <- function(x) c("I am father.")
  4. foo <- structure(1, class = c("son", "father"))
  5. bar(foo)
  1. [1] "I am son." "I am father."
  2. 作者:LeoinUSA
  3. 链接:https://www.jianshu.com/p/1caf31c2289d
  4. 来源:简书
  5. 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

当然,如果没有NextMethod 声明,则会优先使用在前的类的方法。

  1. > bar <- function(x) UseMethod("bar")
  2. > bar.son <- function(x) c("I am son.")
  3. > bar.father <- function(x) c("I am father.")
  4. > foo <- structure(1, class = c("son", "father"))
  5. > bar(foo)
  6. [1] "I am son."

S4 对象

相比S3 对象,S4 对象有明确的类定义、参数定义、参数检查、继承关系、实例化、接口函数、实现函数等面向对象系统的特征。

特点

S4 对象可以创建插槽(slot),我们可以通过插槽在S4 对象中储存信息。并通过@ 方式访问。

比如:

  1. x@Data

S4 也可以像S4 对象一样实现继承。比如说,如果S4 类对象A 包含了B 的全部插槽,我们就认为A 包含(contains)B 对象。

那么更强大的S4 会替代S3 吗?

不知道。

有很多历史遗留的函数都是S3 对象,比如lm 函数等。而且S3 对象非常方便使用。我们无需关注对象的具体S3 类类型,就可以执行相关S3函数了。

因此,S3 对象更适合那些临时的快速的项目(ad hoc);而S4 对象则适合大型的复杂的工程。

创建S4 对象

其他对象

这个inferno 作者是真的🐶!

08. R 的面向对象 - 图1