前言
1. 面向对象
面向对象编程(object-oriented programming
,OOP
)是一种编程范式,它将对象作为程序的基本单元,一个对象包含了数据以及操作数据的函数。
那什么是对象?对象是类(class
)类的实例。
那什么又是类呢?
类是对现实事物的抽象,比如说,人类是对世界上所有人的总称,而你、我却是实实在在存在于现实中的,也就是一个个对象。
类的定义包含了对数据的描述以及对应的操作方法,比如,人应该有性别、年龄、身高、体重等固有特征,但是每个对象,也就是说虽然每个人的特征千差万别,但都有这些固定的属性客观存在的。
2. R 的面向对象编程
之前,我们对 R
的理解可能都是停留在函数式编程的概念里。也就是编写一个个函数,来处理不同的对象。
当然,目前 R
主要用于统计计算,而且代码量一般不会很大,几十或上百行。使用函数式的编程方式就可以很好的完成编程任务。
一般来说,在 R
中,函数式编程要比面向对象编程重要得多,因为你通常是将复杂的问题分解成简单的函数,而不是简单的对象。
那为什么我还要学习面向对象编程呢?
面向对象编程的优势是,能够使程序便于分析、设计、理解,提高重用性、灵活性和可扩展性。
R
中的 OOP
系统
base R
提供的:S3
,S4
和reference classes
(RC
)CRAN
包提供的:R6
、R.oo
、proto
S3
1.1 概念
S3
面向对象编程,是 R
中第一个也是最简单的 OOP
系统,广泛存在于早期开发的 R
包中,也是 CRAN
包最常用的系统。
S3
的实现是基于一种特殊的函数(泛型函数,根据传入对象的类型来决定调用哪个方法)
1.2 创建 S3 对象
注意:下面我们会使用 sloop
包提供的函数来帮助我们查看对象的类型
> install.packages('sloop')
> library(sloop)
首先,我们使用 attr
来创建一个 S3
对象
> a <- 1
> attr(a, 'class') <- 'bar'
> a
[1] 1
attr(,"class")
[1] "bar"
使用 class
或 attr
获取对象的类型
> class(a)
[1] "bar"
> attr(a, 'class')
[1] "bar"
再用 sloop
包的 otype
来判断是何种对象
> otype(a)
[1] "S3"
> otype(1)
[1] "base"
我们也可以使用 structure
来构建一个 S3
对象
> b <- structure(2, class='foo')
> b
[1] 2
attr(,"class")
[1] "foo"
> otype(b)
[1] "S3"
还可以使用为 class(var)
赋值的方式构建
> x <- list(a=1)
> class(x)
[1] "list"
> otype(x)
[1] "base"
> class(x) <- 'foo'
> class(x)
[1] "foo"
> otype(x)
[1] "S3"
还可以将类属性设置为向量,为 S3
对象指定多个类型
> c <- structure(3, class=c('bar', 'foo'))
> class(c)
[1] "bar" "foo"
> otype(c)
[1] "S3"
1.3 创建泛型函数
通常,我们使用 UseMethod()
来创建一个泛型函数,例如
person <- function(x, ...) {
UseMethod('person')
}
定义完泛型函数之后,可以使用以下方式
-
person.xxx
定义名为xxx
的方法 -
person.default
定义默认方法
person.default <- function(x, ...) {
print("I am human.")
}
person.sing <- function(x, ...) {
print("I can sing")
}
person.name <- function(x, ...) {
print(paste0("My name is ", x))
}
那如何调用这些方法呢?
首先,我们定义一个 class
属性为 "sing"
的变量
> a <- structure("tom", class='sing')
然后,将该对象 a
传入 person
中
> person(tom)
[1] "I can sing"
> person.sing(a)
[1] "I can sing"
可以看到,调用了 person.sing()
方法。
让我们再尝试其他类型
> b <- structure("tom", class='name')
> person(b)
[1] "My name is tom"
> person("joy")
[1] "I am human."
这样,我们只要使用 person
函数,就能够对不同类型的输入做出相应,输入不同类型的对象会自动调用相应的方法。
对于未指定的类型,会调用 person.default
方法。这就是泛型函数。
1.4 S3 对象的方法
我们可以使用 methods()
函数来获取 S3
对象包含的所有方法
> methods(person)
[1] person.default person.name person.sing
可以使用 generic.function
参数,传递想要查询的泛型函数
> library(magrittr)
> methods(generic.function = print) %>% head()
[1] "print.acf" "print.anova" "print.aov" "print.aovlist"
[5] "print.ar" "print.Arima"
class
参数指定类名
> methods(class = lm) %>% head()
[1] "add1.lm" "alias.lm"
[3] "anova.lm" "case.names.lm"
[5] "coerce,oldClass,S3-method" "confint.lm"
注意:一些输出的函数名后缀有 *
号表示不可见函数,例如
> print.xtabs
错误: 找不到对象'print.xtabs'
可以使用 getAnywhere
获取
> getAnywhere(print.xtabs)
A single object matching ‘print.xtabs’ was found
It was found in the following places
registered S3 method for print from namespace stats
namespace:stats
with value
function (x, na.print = "", ...)
{
ox <- x
attr(x, "call") <- NULL
print.table(x, na.print = na.print, ...)
invisible(ox)
}
<bytecode: 0x7fe1e612b9e8>
<environment: namespace:stats>
或者 getS3method
> getS3method("print", "xtabs")
function (x, na.print = "", ...)
{
ox <- x
attr(x, "call") <- NULL
print.table(x, na.print = na.print, ...)
invisible(ox)
}
<bytecode: 0x7fe1e612b9e8>
<environment: namespace:stats>
1.5 S3 对象的继承
S3
对象是通过 NextMethod()
方法继承的,让我们先定义一个泛型函数
person <- function(x, ...) {
UseMethod('person')
}
person.father <- function(x, ...) {
print("I am father.")
}
person.son <- function(x, ...) {
NextMethod()
print("I am son.")
}
执行
> p1 <- structure(1,class=c("father"))
> person(p1)
[1] "I am father."
> p2 <- structure(1,class=c("son","father"))
> person(p2)
[1] "I am father."
[1] "I am son."
可以看到,在调用 person(p2)
之后,会先执行 person.father()
然后执行 person.son()
注意:需要将被继承的类型放在第二个(son
之后)
> ab <- structure(1, class = c("father", "son"))
> person(ab)
[1] "I am father."
这样就实现了面向对象编程中的继承
1.6 缺点
-
S3
并不是完全的面向对象,而是基于泛型函数模拟的面向对象 -
S3
用起来简单,但是对于复杂的对象关系,很难高清对象的意义 - 缺少检查,
class
属性可以被任意设置
1.7 示例
S3
对象系统广泛存在于 R
语言的早期开发中,因此,在 base
包中包含了许多 S3
对象。
例如
> ftype(plot)
[1] "S3" "generic"
> ftype(print)
[1] "S3" "generic"
自定义 S3
对象
say <- function(x, ...) {
UseMethod("say")
}
say.numeric <- function(x, ...) {
paste0("the number is ", x)
}
say.character <- function(x, ...) {
paste0("the character is ", x)
}
使用
> say('nam')
[1] "the character is nam"
> say(12315)
[1] "the number is 12315"