R 面向对象编程(一)

前言

1. 面向对象

面向对象编程(object-oriented programmingOOP)是一种编程范式,它将对象作为程序的基本单元,一个对象包含了数据以及操作数据的函数。

那什么是对象?对象是类(class)类的实例。

那什么又是类呢?

类是对现实事物的抽象,比如说,人类是对世界上所有人的总称,而你、我却是实实在在存在于现实中的,也就是一个个对象。

类的定义包含了对数据的描述以及对应的操作方法,比如,人应该有性别、年龄、身高、体重等固有特征,但是每个对象,也就是说虽然每个人的特征千差万别,但都有这些固定的属性客观存在的。

2. R 的面向对象编程

之前,我们对 R 的理解可能都是停留在函数式编程的概念里。也就是编写一个个函数,来处理不同的对象。

当然,目前 R 主要用于统计计算,而且代码量一般不会很大,几十或上百行。使用函数式的编程方式就可以很好的完成编程任务。

一般来说,在 R 中,函数式编程要比面向对象编程重要得多,因为你通常是将复杂的问题分解成简单的函数,而不是简单的对象。

那为什么我还要学习面向对象编程呢?

面向对象编程的优势是,能够使程序便于分析、设计、理解,提高重用性、灵活性和可扩展性。

R 中的 OOP 系统

  • base R 提供的:S3, S4reference classes (RC)

  • CRAN 包提供的:R6R.ooproto

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"

使用 classattr 获取对象的类型

> 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 缺点

  1. S3 并不是完全的面向对象,而是基于泛型函数模拟的面向对象
  2. S3 用起来简单,但是对于复杂的对象关系,很难高清对象的意义
  3. 缺少检查,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"
  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

名本无名

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值