任何面向对象系统的核心都是类和方法的概念。 通过描述类的属性以及它与其它类的关系, 类定义了对象的行为。 类也用于选择方法,函数会根据输入的不同的类,表现出不同的行为。 类通常被组织成层次结构:如果某个子类的方法不存在,那么将使用父类的方法。 子类从父类那里继承行为
R中共有三种面向对象系统,分别为S3
, S4
, RC
基本类型
每个 R 语言对象的底层都是一个 C 语言结构(即 C 语言的 struct),它描述了该对象是如何存储在内存中的。 该结构包括对象的内容、 内存管理所需的信息
最常见的基本类型(原子向量和列表),但是基本类型还包括函数、环境和其它更多的特殊对象,比如名字(name)、 调用(call)和承诺(promise)
使用typeof()
确定对象的基本类型
a <- c(1,3,4,5)
d <- matrix(1:6,2,3)
e <- list(a1 = a, a2 = d)
f <- function(){}
> typeof(e)
[1] "list"
> typeof(a)
[1] "double"
> typeof(sum)
[1] "builtin"
> typeof(f)
[1] "closure"
R 中使用的基本类型的名称并不总是一致的,而且,类型和相应的”is”函数也可能使用不同的名称
f <- function(){}
> typeof(f)
[1] "closure"
> is.function(f)
[1] TRUE
S3
对象可以建立在任何基本类型之上, S4
对象使用了一种特殊的基本类型,而引用类对象是 S4 和环境(另一个基本类型)的组合体。 要查看一个对象是不是一个纯粹的基本类型,即它没有S3
、 S4
或 RC
的行为,可以检查is.object(x)
是不是返回 FALSA
S3
S3简介
S3
是R中最简答的一种面向对象系统,与其他语言的面向对象不同,
java
,C++
python
等: 实现了消息传递的面向对象系统。 通过消息传递,消息(方法)被发送到对象,并且由对象来决定要调用哪一个函数。 通常,这个对象在方法调用的时候,有特别的展现方式,它通常出现在方法或消息的名称之前:如canvas.drawRect("blue")
S3
: 由一种称为泛型函数的特殊类型函数来决定调用哪个方法, 如drawRect(canvas, "blue")
可以通过pryr::otype()
判断一个对象是不是S3
a <- c(1,3,4,5)
f <- function(){}
> pryr::otype(a)
[1] "base"
> pryr::otype(f)
[1] "base"
> pryr::otype(data.frame())
[1] "S3"
泛型函数和方法
在 S3 中, 方法属于函数,称为泛型函数(generic functions),或简称为泛型(generics)。 S3 的方法不属于对象或类。
使用pryr::ftype()
查看某函数是否为泛型函数
> pryr::ftype(mean)
[1] "s3" "generic"
可以通过名字来认识 S3 方法, 例如, Date
类的 mean()
泛型函数是mean.Date()
,而 factor
类的 print()
泛型函数是 print.factor()
通过pryr::ftype()
查出一个函数是S3 方法
还是泛型函数
> pryr::ftype(t.data.frame) # 数据框的 t()方法
[1] "s3" "method"
> #> [1] "s3" "method"
> pryr::ftype(t.test) # t 检验的泛型函数
[1] "s3" "generic"
使用methods()
函数查看某个泛型函数的所有方法
> methods(mean)
[1] mean.Date mean.default mean.difftime mean.POSIXct mean.POSIXlt
see '?methods' for accessing help and source code
> methods(print)
[1] print.acf*
[2] print.anova*
[3] print.aov*
...
创建S3类
S3 是一个简单的系统;它没有类的正式定义。 要创建一个类的实例,只需要拿一个已有的基本对象(S3 对象通常是建立在列表之上或者是带有属性的原子向量),并设置它的class 属性即可。使用下面两个函数创建
structure()
class()<-
inherits(x, "classname")
: 判断某个类是否继承自某个类 ```r foo <- structure(list(), class = “foo”)
foo2 <- list() class(foo2)<- “foo2”
foo list() attr(,”class”) [1] “foo” foo2 list() attr(,”class”) [1] “foo2”
例如, `glm()`对象的类是 `c("glm", "lm")`,表明广义线性模型(`generalised linear models`)从线性模型(`linear models`)继承了行为。
```r
counts <- c(18,17,15,20,10,20,25,13,12)
outcome <- gl(3,1,9)
treatment <- gl(3,3)
data.frame(treatment, outcome, counts) # showing data
glm.D93 <- glm(counts ~ outcome + treatment, family = poisson())
class(glm.D93)
[1] "glm" "lm"
构造函数
如果构造函数存在(比如
factor()
和data.frame()
),那么就应该使用构造函数创建对象,确保你使用正确的组件来创建的类的实例,S3 并不检查类的正确性。 这意味着,可以改变现有对象的类,但还是别这么搞了,会挨打
foo <- function(x) {
if (!is.numeric(x)) stop("X must be numeric")
structure(list(x), class = "foo")
}
foo(1)
创建新的方法和泛型函数
要添加一个新的泛型函数,可以创建一个函数,然后调用
UseMethod()
。UseMethod()
有两个参数: 泛型函数的名称,以及用于方法分派的参数
泛型函数: 根据不同的类会有不同的行为,类似于多态的调用同一函数,产生了不同的行为? 个人理解
省略了第二个参数,那么它将分派到函数的第一个参数。 不需要对** UseMethod()**
传递任何泛型函数的参数
如果没有一些具体的方法,那么泛型函数是没用的。 要添加一个方法,你只需使用正确的名称(generic.class(对象的class属性值,而不是类名))创建一个普通函数,default
为找不到方法时调用的方法,即,default
代表未知的类,class有多个值的时候,默认第一个 ```r创建泛型函数f
f <- function(x) {UseMethod(“f”)}
创建foo类
My_Class <- structure(list(), class = “My_Class2”)
创建新方法
f.My_Class2 <- function(x){ print(“f.My_Class method”) }
f.default <- function(x){ print(“f.default method”) } f(My_Class) [1]”f.My_Class method”
为已有的泛型函数添加一个方法,方式是相同的
mean.My_Class <- function(x){ print(“mean”) } mean(My_Class) [1] “mean”
<a name="O3CoZ"></a>
## S4
<a name="EfWBB"></a>
### S4简介
`S4 `以类似于 `S3 `的方式工作,但它更加正式和严谨。** 在 S4 中,方法仍然是属于函数的,而不是类**
- `S4 `类有正式的定义,描述了它们的字段(`**fields**`)和继承结构(`**inheritancestructures**`) (即, 父类(`**parent classes**`))。
- 可以对泛型函数进行基于多个参数的方法分派,而不只是一个
- 有一个特殊的操作符, `@`,它从 `S4 `对象中提取槽(`slot`)(又名字段)(类的属性)的数据。
<a name="VzZIj"></a>
### 创建S4类
必须使用 `setClass()`定义一个类的表示方式,然后使用`new()`创建一个新对象, 如果构造函数存在则应使用构造函数, 同时可以使用 `setOldClass()`进行注册的 S3 类 <br />一个`S4`类有三个关键属性
- 名字: `UpperCamelCase`风格
- `slot`: 一个**命名列表**表示的一些`slot`(字段),它定义了槽的名称和允许的类,可以使用`@`或 `slot()`来访问 S4 对象的槽 ,如下
```r
list(name ="character", age = "numeric")。
contain
: 指出继承自哪个类,支持多重继承
S4 类有其它可选的属性,比如测试对象是否有效的 validity
方法
# 创建Pearson类
setClass("Pearson", slots = list(
name = "character",
age = "numeric"
))
# 创建 Employee类, 继承了Pearson类,boos slot接受一个Pearson类作为值
setClass("Employee", slots = list(
boss = "Pearson"
),contains = "Pearson")
Tom <- new("Pearson", name = "Tom", age = 23)
Tom2 <- new("Employee", name = "Tom", age = 23, boss = Tom)
如果S4 对象包含(继承自)S3 类或者基本类型,就会有一个.Data
slot包含底层的基本类型或者 S3 对象
setClass("RangedNumeric",
contains = "numeric",
slots = list(min = "numeric", max = "numeric"))
rn <- new("RangedNumeric", 1:10, min = 1, max = 10)
rn@.Data
[1] 1 2 3 4 5 6 7 8 9 10
创建新的方法和泛型函数
使用setGeneric()
和setMethod()
创建新的泛型方法和方法
setGeneric()
: 创建一个新的泛型函数,或者将现有的函数转换成一个泛型函数setMethod()
: 则需要泛型函数的名称、 方法应该关联的类以及一个实现该方法的函数
如果仅对有单个父类的单个 S4 类进行泛型函数分派,则 S4 的方法分派与 S3 相同。 主要的区别是如何设置默认值: S4 使用特殊类 ANY 来匹配任何类,以及使用”missing”来匹配一个缺失参数。
setGeneric("union")
setMethod("union",
c(x = "data.frame", y = "data.frame"),
function(x, y) {
unique(rbind(x, y))
} )
methods(union)
# [1] union,ANY,ANY-method union,data.frame,data.frame-method
# test
df1 <- data.frame(a = c(1,2,3,4,5))
df2 <- data.frame(a = c(1,2,3,4,6))
union(df1, df2)
a
1 1
2 2
3 3
4 4
5 5
10 6
引用类
引用类简介
引用类(Reference Class,简称 RC)是 R 语言中最新的面向对象系统。 它们是在 R语言的 2.12 版中引入的。 它们完全不同于 S3 和 S4,这是因为
- 引用类的方法属于对象,而不是函数
- 引用类是可变的对象: R 语言中通常的修改时复制语义对它不适用
引用类是使用 R 代码实现的:它们是封装在一个环境中的特殊 S4 类
创建引用类
使用setRefClass()
创建RC类,通常使用setRefClass()
的返回值作为RC类的对象,new()
也可以
Account <- setRefClass("Account")
> Account
Generator for class "Account":
No fields defined
Class Methods:
"field", "trace", "getRefClass", "initFields", "copy", "callSuper", ".objectPackage",
"export", "untrace", "getClass", "show", "usingMethods", ".objectParent", "import"
Reference Superclasses:
"envRefClass"
定义了引用类的字段fields
(相当于 S4 的slot)。 额外的命名参数传递给 new(),将设置字段的初始值。 你可以使用$来存取字段值
Account <- setRefClass("Account",
fields = list(balance = "numeric"))
a <- Account$new(balance = 100)
a$balance
使用methods
选项可以定义方法, 使用$
即可调用方法
Account <- setRefClass("Account",
fields = list(balance = "numeric"),
methods = list(
withdraw = function(x) {
balance <<- balance - x
},
deposit = function(x) {
balance <<- balance + x
} ) )
setRefClass()最
后一个重要的参数是 contains
。 这是一个父引用类的名字,当前类是从它继承过来的。
NoOverdraft <- setRefClass("NoOverdraft",
contains = "Account",
methods = list(
withdraw = function(x) {if (balance < x) stop("Not enough money")
balance <<- balance - x
} ) )
当你调用x$f()
的时候, R 将在类x
中寻找方法 f
,如果没有找到 f
,则在 x
的父类中寻找,如果仍然没有找到,则在 x
的父类的父类中寻找,以此类推。 在方法内部,你可以使用 callSuper(...)
直接调用父类的方法