R语言-S3系统

本文参考R语言基于S3的面向对象编程

R的面向对象编程提供了3种底层对象类型,一种是S3类型,一种是S4类型还有一种是RC类型。
S3对象简单、具有动态性、结构化特征不明显;S4对象结构化、
功能强大;RC对象是2.12版本后使用的新类型,用于解决S3,S4很难实现的对象。

1.S3对象介绍
在R语言中,基于S3对象的面向对象编程,是一种基于泛型函数的实现方式。泛型函数是一种特殊的函数, 根据传入对象的类型决定调用哪个具体的方法。基于S3对象实现的面向对象编程,不同其他语言的面向对象编程,是一种动态函数调用的模拟实现。S3对象被广泛应用于R的早期的开发包中。

2.创建S3对象
以下有许多检测类的函数都是pryr包的内容,记得先加载哦~
(1) 通过变量创建S3对象

> x<-1
> attr(x,'class')<-'foo'   为了x增加class属性

> x
[1] 1
attr(,"class")
[1] "foo"

> class(x)
[1] "foo"

# 用pryr包的otype函数,检查x的类型
> otype(x)
[1] "S3"

(2) 通过structure函数创建S3对象

> y <- structure(2, class = "foo")

> y
[1] 2
attr(,"class")
[1] "foo"

> class(y)
[1] "foo"

> otype(y)
[1] "S3"

(3) 创建一个多类型的S3对象
S3对象没有明确结构关系,一个S3对象可以有多个类型, S3对象的 class 属性可以是一个向量,包括多种类型。

> x<-1
> attr(x,'class')<- c("foo", "bar")
> class(x)
[1] "foo" "bar"
> otype(x)
[1] "S3"

3.泛型函数和方法调用
对于S3对象的使用,通常用UseMethod()函数来定义一个泛型函数的名称,通过传入参数的class属性,来确定不同的方法调用。

定义一个teacher的泛型函数

  • 用UseMethod()定义teacher泛型函数
  • 用teacher.xxx的语法格式定义teacher对象的行为
  • 其中teacher.default是默认行为
# 用UseMethod()定义teacher泛型函数
> teacher <- function(x, ...) UseMethod("teacher")

# 用pryr包中ftype()函数,检查teacher的类型
> ftype(teacher)
[1] "s3"      "generic"

# 定义teacher内部函数
> teacher.lecture <- function(x) print(paste(x,"讲课"))
> teacher.assignment <- function(x) print(paste(x,"布置作业"))
> teacher.correcting <- function(x) print(paste(x,"批改作业"))
> teacher.default<-function(x) print("你不是teacher")

S3所谓的类其实只是将一系列同类动作封装在了teacher泛化函数下,并没有很强的结构关系,各函数间也没有什么联系。而且并不存在所谓个体是类的实例化的概念,调用的方法并不是对象本身的,而是泛化函数的。

方法调用时,通过传入参数的class属性,来确定不同的方法调用。

  • 定义一个变量a,并设置a的class属性为lecture
  • 把变量a,传入到teacher泛型函数中
  • 函数teacher.lecture()函数的行为被调用
> a<-"张三"
> attr(a,"class")<-"lecture"
> teacher(a)
[1] "张三 讲课"

当然我们也可以直接调用teacher中定义好的函数,因为S3的实现策略并不需要对象产生必要的实例化动作,但这样做它仅有的面向对象封装的意义也将失去。

> teacher.default()
[1] "你不是teacher"

4.查看S3对象的函数
当我们使用S3对象进行面向对象封装后,可以用methods()函数来查看S3对象中的定义的内部行为函数。

> teacher
function(x,...)UseMethod("teacher")
> methods(teacher)
[1] teacher.assignment teacher.correcting teacher.default teacher.lecture   

通过methods()的generic.function参数,来匹配泛型函数名字。

methods(generic.function=predict)
 [1] predict.ar*                predict.Arima*             predict.arima0*            predict.glm               
 [5] predict.glmmPQL*           predict.HoltWinters*       predict.lda*               predict.lm                
 [9] predict.loess*             predict.lqs*               predict.mca*               predict.mlm* 
……            

通过methods()的class参数,来匹配类的名字。

> methods(class=lm)
 [1] add1.lm*           alias.lm*          anova.lm           case.names.lm*
 [5] confint.lm*        cooks.distance.lm* deviance.lm*       dfbeta.lm*
 [9] dfbetas.lm*        drop1.lm*          dummy.coef.lm*     effects.lm*
……

用getAnywhere()函数,查看所有的函数。

> getAnywhere(teacher.lecture())
A single object matching ‘teacher.lecture’ was found
It was found in the following places
  .GlobalEnv
  registered S3 method for teacher
with value

function(x) print(paste(x,"讲课"))

# 查看不可见的函数predict.ppr
> predict.ppr
Error: object 'predict.ppr' not found
> exists("predict.ppr")
[1] FALSE

# getAnywhere()函数查找predict.ppr
> getAnywhere("predict.ppr")
A single object matching ‘predict.ppr’ was found
It was found in the following places
  registered S3 method for predict from namespace stats
  namespace:stats
with value

function (object, newdata, ...)
{
    if (missing(newdata))
        return(fitted(object))
    if (!is.null(object$terms)) {
        newdata <- as.data.frame(newdata)
        rn <- row.names(newdata)
        ……

可以看出来teacher.lecture()函数是被存储在全局环境中的,可见泛化函数与具体动作函数之间并不是真正的包含关系。
使用getS3method()函数,也同样可以查看不可见的函数,效果类似

5.S3对象的继承关系
S3对象有一种非常简单的继承方式,用NextMethod()函数来实现。

定义一个 node泛型函数

> node <- function(x) UseMethod("node", x)
> node.default <- function(x) "Default node"

# father函数
> node.father <- function(x) c("father")

# son函数,通过NextMethod()函数指向father函数
> node.son <- function(x) c("son", NextMethod())

# 定义n1
> n1 <- structure(1, class = c("father"))
# 在node函数中传入n1,执行node.father()函数
> node(n1)
[1] "father"

# 定义n2,设置class属性为两个
> n2 <- structure(1, class = c("son", "father"))
# 在node函数中传入n2,执行node.son()函数和node.father()函数
> node(n2)
[1] "son"    "father"

通过node函数传入n2的过程,可以发现S3中的所谓继承,就是通过子函数中NextMethod()函数允许,继续调用另一个class所代表的函数,而并不是在函数内部真的完成了调用,是通过内外的“配合”才实现的

6.S3对象的缺点

  • S3使用起来简单,但在实际的面向对象编程过程中,当对象关系有一定的复杂度,S3对象所表达的意义就会变得不太清楚。
  • S3封装的内部函数,可绕过泛型函数的检查,以直接被调用。
  • S3参数的class属性,可以被任意设置,没有预处理的检查。
  • S3参数,只能通过调用class属性进行函数调用,其他属性则不会被class()函数执行。
  • S3参数的class属性有多个值时,调用时会按照程序赋值顺序来调用第一个合法的函数。
    所以,S3只能R语言面向对象的一种简单的实现。

有了这些知识,你就可以应用S3的技术进行R的开发了,当然这只是让你的代码看起来更封装性更好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值