面向对象 - 图1
任何面向对象系统的核心都是类和方法的概念。 通过描述类的属性以及它与其它类的关系, 类定义了对象的行为。 类也用于选择方法,函数会根据输入的不同的类,表现出不同的行为。 类通常被组织成层次结构:如果某个子类的方法不存在,那么将使用父类的方法。 子类从父类那里继承行为
R中共有三种面向对象系统,分别为S3, S4, RC

基本类型

每个 R 语言对象的底层都是一个 C 语言结构(即 C 语言的 struct),它描述了该对象是如何存储在内存中的。 该结构包括对象的内容、 内存管理所需的信息
最常见的基本类型(原子向量和列表),但是基本类型还包括函数、环境和其它更多的特殊对象,比如名字(name)、 调用(call)和承诺(promise)
使用typeof()确定对象的基本类型

  1. a <- c(1,3,4,5)
  2. d <- matrix(1:6,2,3)
  3. e <- list(a1 = a, a2 = d)
  4. f <- function(){}
  5. > typeof(e)
  6. [1] "list"
  7. > typeof(a)
  8. [1] "double"
  9. > typeof(sum)
  10. [1] "builtin"
  11. > typeof(f)
  12. [1] "closure"

R 中使用的基本类型的名称并不总是一致的,而且,类型和相应的”is”函数也可能使用不同的名称

  1. f <- function(){}
  2. > typeof(f)
  3. [1] "closure"
  4. > is.function(f)
  5. [1] TRUE

S3对象可以建立在任何基本类型之上, S4 对象使用了一种特殊的基本类型,而引用类对象是 S4 和环境(另一个基本类型)的组合体。 要查看一个对象是不是一个纯粹的基本类型,即它没有S3S4RC 的行为,可以检查is.object(x)是不是返回 FALSA

S3

S3简介

S3是R中最简答的一种面向对象系统,与其他语言的面向对象不同,

  • java, C++ python等: 实现了消息传递的面向对象系统。 通过消息传递,消息(方法)被发送到对象,并且由对象来决定要调用哪一个函数。 通常,这个对象在方法调用的时候,有特别的展现方式,它通常出现在方法或消息的名称之前:如 canvas.drawRect("blue")
  • S3: 由一种称为泛型函数的特殊类型函数来决定调用哪个方法, 如drawRect(canvas, "blue")

可以通过pryr::otype()判断一个对象是不是S3

  1. a <- c(1,3,4,5)
  2. f <- function(){}
  3. > pryr::otype(a)
  4. [1] "base"
  5. > pryr::otype(f)
  6. [1] "base"
  7. > pryr::otype(data.frame())
  8. [1] "S3"

泛型函数和方法

在 S3 中, 方法属于函数,称为泛型函数(generic functions),或简称为泛型(generics)。 S3 的方法不属于对象或类。
使用pryr::ftype()查看某函数是否为泛型函数

  1. > pryr::ftype(mean)
  2. [1] "s3" "generic"

可以通过名字来认识 S3 方法, 例如, Date类的 mean()泛型函数是mean.Date(),而 factor类的 print()泛型函数是 print.factor()
通过pryr::ftype()查出一个函数是S3 方法还是泛型函数

  1. > pryr::ftype(t.data.frame) # 数据框的 t()方法
  2. [1] "s3" "method"
  3. > #> [1] "s3" "method"
  4. > pryr::ftype(t.test) # t 检验的泛型函数
  5. [1] "s3" "generic"

使用methods()函数查看某个泛型函数的所有方法

  1. > methods(mean)
  2. [1] mean.Date mean.default mean.difftime mean.POSIXct mean.POSIXlt
  3. see '?methods' for accessing help and source code
  4. > methods(print)
  5. [1] print.acf*
  6. [2] print.anova*
  7. [3] print.aov*
  8. ...

创建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”

  1. 例如, `glm()`对象的类是 `c("glm", "lm")`,表明广义线性模型(`generalised linear models`)从线性模型(`linear models`)继承了行为。
  2. ```r
  3. counts <- c(18,17,15,20,10,20,25,13,12)
  4. outcome <- gl(3,1,9)
  5. treatment <- gl(3,3)
  6. data.frame(treatment, outcome, counts) # showing data
  7. glm.D93 <- glm(counts ~ outcome + treatment, family = poisson())
  8. class(glm.D93)
  9. [1] "glm" "lm"

构造函数

如果构造函数存在(比如factor()data.frame()),那么就应该使用构造函数创建对象,确保你使用正确的组件来创建的类的实例,S3 并不检查类的正确性。 这意味着,可以改变现有对象的类,但还是别这么搞了,会挨打

  1. foo <- function(x) {
  2. if (!is.numeric(x)) stop("X must be numeric")
  3. structure(list(x), class = "foo")
  4. }
  5. 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”

  1. <a name="O3CoZ"></a>
  2. ## S4
  3. <a name="EfWBB"></a>
  4. ### S4简介
  5. `S4 `以类似于 `S3 `的方式工作,但它更加正式和严谨。** 在 S4 中,方法仍然是属于函数的,而不是类**
  6. - `S4 `类有正式的定义,描述了它们的字段(`**fields**`)和继承结构(`**inheritancestructures**`) (即, 父类(`**parent classes**`))。
  7. - 可以对泛型函数进行基于多个参数的方法分派,而不只是一个
  8. - 有一个特殊的操作符, `@`,它从 `S4 `对象中提取槽(`slot`)(又名字段)(类的属性)的数据。
  9. <a name="VzZIj"></a>
  10. ### 创建S4类
  11. 必须使用 `setClass()`定义一个类的表示方式,然后使用`new()`创建一个新对象, 如果构造函数存在则应使用构造函数, 同时可以使用 `setOldClass()`进行注册的 S3 类 <br />一个`S4`类有三个关键属性
  12. - 名字: `UpperCamelCase`风格
  13. - `slot`: 一个**命名列表**表示的一些`slot`(字段),它定义了槽的名称和允许的类,可以使用`@`或 `slot()`来访问 S4 对象的槽 ,如下
  14. ```r
  15. list(name ="character", age = "numeric")。
  • contain: 指出继承自哪个类,支持多重继承

S4 类有其它可选的属性,比如测试对象是否有效的 validity方法

  1. # 创建Pearson类
  2. setClass("Pearson", slots = list(
  3. name = "character",
  4. age = "numeric"
  5. ))
  6. # 创建 Employee类, 继承了Pearson类,boos slot接受一个Pearson类作为值
  7. setClass("Employee", slots = list(
  8. boss = "Pearson"
  9. ),contains = "Pearson")
  10. Tom <- new("Pearson", name = "Tom", age = 23)
  11. Tom2 <- new("Employee", name = "Tom", age = 23, boss = Tom)

如果S4 对象包含(继承自)S3 类或者基本类型,就会有一个.Dataslot包含底层的基本类型或者 S3 对象

  1. setClass("RangedNumeric",
  2. contains = "numeric",
  3. slots = list(min = "numeric", max = "numeric"))
  4. rn <- new("RangedNumeric", 1:10, min = 1, max = 10)
  5. rn@.Data
  6. [1] 1 2 3 4 5 6 7 8 9 10

创建新的方法和泛型函数

使用setGeneric()setMethod()创建新的泛型方法和方法

  • setGeneric(): 创建一个新的泛型函数,或者将现有的函数转换成一个泛型函数
  • setMethod(): 则需要泛型函数的名称、 方法应该关联的类以及一个实现该方法的函数

如果仅对有单个父类的单个 S4 类进行泛型函数分派,则 S4 的方法分派与 S3 相同。 主要的区别是如何设置默认值: S4 使用特殊类 ANY 来匹配任何类,以及使用”missing”来匹配一个缺失参数。

  1. setGeneric("union")
  2. setMethod("union",
  3. c(x = "data.frame", y = "data.frame"),
  4. function(x, y) {
  5. unique(rbind(x, y))
  6. } )
  7. methods(union)
  8. # [1] union,ANY,ANY-method union,data.frame,data.frame-method
  9. # test
  10. df1 <- data.frame(a = c(1,2,3,4,5))
  11. df2 <- data.frame(a = c(1,2,3,4,6))
  12. union(df1, df2)
  13. a
  14. 1 1
  15. 2 2
  16. 3 3
  17. 4 4
  18. 5 5
  19. 10 6

引用类

引用类简介

引用类(Reference Class,简称 RC)是 R 语言中最新的面向对象系统。 它们是在 R语言的 2.12 版中引入的。 它们完全不同于 S3 和 S4,这是因为

  • 引用类的方法属于对象,而不是函数
  • 引用类是可变的对象: R 语言中通常的修改时复制语义对它不适用

引用类是使用 R 代码实现的:它们是封装在一个环境中的特殊 S4 类

创建引用类

使用setRefClass()创建RC类,通常使用setRefClass()的返回值作为RC类的对象,new()也可以

  1. Account <- setRefClass("Account")
  2. > Account
  3. Generator for class "Account":
  4. No fields defined
  5. Class Methods:
  6. "field", "trace", "getRefClass", "initFields", "copy", "callSuper", ".objectPackage",
  7. "export", "untrace", "getClass", "show", "usingMethods", ".objectParent", "import"
  8. Reference Superclasses:
  9. "envRefClass"

定义了引用类的字段fields(相当于 S4 的slot)。 额外的命名参数传递给 new(),将设置字段的初始值。 你可以使用$来存取字段值

  1. Account <- setRefClass("Account",
  2. fields = list(balance = "numeric"))
  3. a <- Account$new(balance = 100)
  4. a$balance

使用methods选项可以定义方法, 使用$即可调用方法

  1. Account <- setRefClass("Account",
  2. fields = list(balance = "numeric"),
  3. methods = list(
  4. withdraw = function(x) {
  5. balance <<- balance - x
  6. },
  7. deposit = function(x) {
  8. balance <<- balance + x
  9. } ) )

setRefClass()最后一个重要的参数是 contains。 这是一个父引用类的名字,当前类是从它继承过来的。

  1. NoOverdraft <- setRefClass("NoOverdraft",
  2. contains = "Account",
  3. methods = list(
  4. withdraw = function(x) {if (balance < x) stop("Not enough money")
  5. balance <<- balance - x
  6. } ) )

当你调用x$f()的时候, R 将在类x 中寻找方法 f,如果没有找到 f,则在 x的父类中寻找,如果仍然没有找到,则在 x 的父类的父类中寻找,以此类推。 在方法内部,你可以使用 callSuper(...)直接调用父类的方法