
任何面向对象系统的核心都是类和方法的概念。 通过描述类的属性以及它与其它类的关系, 类定义了对象的行为。 类也用于选择方法,函数会根据输入的不同的类,表现出不同的行为。 类通常被组织成层次结构:如果某个子类的方法不存在,那么将使用父类的方法。 子类从父类那里继承行为
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.POSIXltsee '?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`)继承了行为。```rcounts <- 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 dataglm.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 对象的槽 ,如下```rlist(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 类或者基本类型,就会有一个.Dataslot包含底层的基本类型或者 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# testdf1 <- data.frame(a = c(1,2,3,4,5))df2 <- data.frame(a = c(1,2,3,4,6))union(df1, df2)a1 12 23 34 45 510 6
引用类
引用类简介
引用类(Reference Class,简称 RC)是 R 语言中最新的面向对象系统。 它们是在 R语言的 2.12 版中引入的。 它们完全不同于 S3 和 S4,这是因为
- 引用类的方法属于对象,而不是函数
- 引用类是可变的对象: R 语言中通常的修改时复制语义对它不适用
引用类是使用 R 代码实现的:它们是封装在一个环境中的特殊 S4 类
创建引用类
使用setRefClass()创建RC类,通常使用setRefClass()的返回值作为RC类的对象,new()也可以
Account <- setRefClass("Account")> AccountGenerator for class "Account":No fields definedClass 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(...)直接调用父类的方法
