参见:《R 的极客理想 高级开发篇》
《R inferno》
R 语言提供了三种底层对象系统,分别是:S3 类型、S4 类型,RC 类型。
- S3 对象简单,具有动态性,但结构化特征不明显;
- S4 对象结构化,功能强大;
- RC 对象用于解决S3、S4 对象难以解决的对象设计。
需要使用的包为:
install.packages(pacman)
pacman::p_load(pryr)
S3 对象
在R 中,我们可以通过attr 来修改对象属性,创建S3 对象的最简单方法,就是修改变量的class 属性为一个自定义值:
> x <- 1
> class(x)
[1] "numeric"
> x
[1] 1
attr(,"class")
[1] "random"
> class(x)
[1] "random"
我们可以通过pryr 包中的函数otype 查看x 类型:
> y <- 1:10
> otype(y)
[1] "base"
> otype(x)
[1] "S3"
S3没有正式的类型间关系的定义,一个对象可以有多个类型,表现为其class属性是一个向量:
> class(x) <- c("x1","x2")
> x
[1] 1
attr(,"class")
[1] "x1" "x2"
方法调用
方法分派是指由泛型函数 (generic function) 来决定对某个对象使用的方法. 所有泛型函数都有类似的形式: 一个广义的函数名, 并调用 UseMethod() 来决定为对象分派哪个方法. 这也使得泛型函数的形式都很简单, 比如 mean() :
function (x, ...)
UseMethod("mean")
<bytecode: 0x103792920>
<environment: namespace:base>
首先通过UseMethod 定义类型,接着会根据对象的 class 属性来决定分派什么方法,所以方法必须以 generic.class 的方式命名才能被 UseMethod() 找到, 比如:
mean.numeric <- function(x, ...) sum(x)/length(x)
mean.data.frame <- function(x, ...) sapply(x, mean, ...)
mean.matrix <- function(x, ...) apply(x, 2, mean)
比如class 为numeric 的对象在使用mean 方法是,调用的就是第一个方法。
下面为例子:
test <- function(x, ...) UseMethod("test")
test.a1 <- function(x, ...) "a1"
test.a2 <- function(x, ...) "a2"
b <- structure(1, class = "a1") # a1 类型,找的是a1 方法
test(b)
c <- structure(1, class = "a3")
test(c) # 没有a3 类型对应的函数,会报错
# Error in UseMethod("test") : "test"没有适用于"a3"目标对象的方法
我们也可以直接调用方法:
> test.a2(c)
[1] "a2"
# 强制执行,并不会报错
我们还可以通过default 设置默认的方法:
> test.default <- function(x, ...) "default"
> test(c)
[1] "default"
如果 class 属性是一个向量 c(”foo”, ”bar”), 则优先寻找mean.foo, 然后 mean.bar, 最后 mean.default。
因此S3 对象使用起来非常的方便,用户并不需要确定具体的对象类型,只需要将对象放到函数中,就会自动寻找相关的类型,并选择相关函数计算。
查看函数及继承
因为S3 对象创建的方法是泛型的generic function,因此如果直接查看:
> test
function(x, ...) UseMethod("test")
我们可以使用methods 函数查看S3 对象函数的全部泛型函数(在当前加载的R 空间内):
> methods(test)
[1] test.a1 test.a2 test.default
see '?methods' for accessing help and source code
接着就可以直接使用函数或用getS3method 获得其源代码:
> getS3method("test", "a1")
function(x, ...) "a1"
或者指定class 类,获得所有S3对象类有关的函数:
> methods(class=lm)
[1] add1 alias anova case.names
[5] coerce confint cooks.distance deviance
[9] dfbeta dfbetas drop1 dummy.coef
[13] effects extractAIC family formula
[17] hatvalues influence initialize kappa
[21] labels logLik model.frame model.matrix
[25] nobs plot predict print
[29] proj qr residuals rstandard
[33] rstudent show simulate slotsFromS3
[37] summary variable.names vcov
see '?methods' for accessing help and source code
因此实际上我们在对lm 类型对象xx 使用如summary(xx) 时,实际上使用的就是函数summary.lm(xx)。
由于 class 属性可以是向量, 所以 S3 中的继承关系自然地表现为 class 属性的前一个分量是后一个的子类.NextMethod() 函数可以使得一系列的方法被依次应用于对象上.
bar <- function(x) UseMethod("bar", x)
bar.son <- function(x) c("I am son.", NextMethod())
bar.father <- function(x) c("I am father.")
foo <- structure(1, class = c("son", "father"))
bar(foo)
[1] "I am son." "I am father."
作者:LeoinUSA
链接:https://www.jianshu.com/p/1caf31c2289d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
当然,如果没有NextMethod 声明,则会优先使用在前的类的方法。
> bar <- function(x) UseMethod("bar")
> bar.son <- function(x) c("I am son.")
> bar.father <- function(x) c("I am father.")
> foo <- structure(1, class = c("son", "father"))
> bar(foo)
[1] "I am son."
S4 对象
相比S3 对象,S4 对象有明确的类定义、参数定义、参数检查、继承关系、实例化、接口函数、实现函数等面向对象系统的特征。
特点
S4 对象可以创建插槽(slot),我们可以通过插槽在S4 对象中储存信息。并通过@ 方式访问。
比如:
x@Data
S4 也可以像S4 对象一样实现继承。比如说,如果S4 类对象A 包含了B 的全部插槽,我们就认为A 包含(contains)B 对象。
那么更强大的S4 会替代S3 吗?
不知道。
有很多历史遗留的函数都是S3 对象,比如lm 函数等。而且S3 对象非常方便使用。我们无需关注对象的具体S3 类类型,就可以执行相关S3函数了。
因此,S3 对象更适合那些临时的快速的项目(ad hoc);而S4 对象则适合大型的复杂的工程。
创建S4 对象
其他对象
这个inferno 作者是真的🐶!