【r<-高级|理论】R-面向对象编程

作者: 王诗翔 | 来源:发表于2017-09-19 20:53 被阅读119次

    一些概念

    这里首先要提及一些概念,然后我们再看具体的实例加以理解。

    每一个单独的对象都可以被称为对应的一个实例(instance)。操作指定类的函数称为方法(method)。

    把程序接口从具体的实现细节中分离开来的过程称为封装

    在OOP(面向对象编程)中,我们可以通过一个类创建出另外一个类,只需要指定新类的不同信息即可,这种方法称为继承。由此衍生出,被继承的类称为父类或超类(superclass),新创建的类称为子类(subclass)。

    在OOP中,允许同一个方法名操纵不同对象并得到不同的结果,称为多态(polymorphism)。

    通过一系列的其他类来创建新类的过程称为组合(composition)。在一些语言中,一个类可以从多个类中继承方法,称为多重继承(multiple inheritance)。

    实例

    大部分其他语言(比如java)的OOP概念都已经包含在R中,但R中具体的语法和结构却有所不同。我们需要通过调用函数setClass来定义一个类,并且需要调用setMethod函数来定义方法。

    我们先看一个简单的例子:

    我们要实现一个类用来表示时间序列,想定义一个对象包含如下信息:

    • 一个数据集合,取自固定周期的时间段
    • 一个开始时间
    • 一个结束时间
    • 时间序列的周期

    对于可以通过某些属性计算出来的属性信息是多余的。我们从定义一个名为“TimeSeries”的新类开始。

    我们将通过一个包含数据、开始时间、结束时间的数值型向量来描述一个时间序列。然后可以通过它们来计算出时间单位、频率和周期。

    作为类的使用者,如何展现这些信息并不重要。但是对于类的实现者来说,则非常重要。

    R语言中对象存储信息的位置称为(slot)。我们将该对象需要包含的槽命名为datastartend。使用setClass函数来创建新类:

    > setClass("TimeSeries",
    +   representation(
    +     data="numeric",
    +     start="POSIXct",
    +     end="POSIXct"
    +     )
    + )
    
    

    representation部分说明了每个槽所包含的R对象的类型。我们使用new函数(针对S4对象的一个泛型构造方法)来新建一个TimeSeries对象。第一个参数名指定类名,其他参数指定槽的值:

    > my.TimeSeries <- new("TimeSeries",
    +   data = c(1,2,3,4,5,6),
    +   start=as.POSIXct("07/01/2009 0:00:00", tz="GMT",
    +                   format="%m/%d/%Y %H:%M:%S"),
    +   end=as.POSIXct("07/01/2009 0:05:00", tz="GMT",
    +                   format="%m/%d/%Y %H:%M:%S")
    + )
    
    > my.TimeSeries
    An object of class "TimeSeries"
    Slot "data":
    [1] 1 2 3 4 5 6
    
    Slot "start":
    [1] "2009-07-01 GMT"
    
    Slot "end":
    [1] "2009-07-01 00:05:00 GMT"
    

    对于一个槽来说,并不是所有的可能值都是有效的。比如,我们想要确保end发生在start之后,并且两者的长度是1。我们需要编写函数来验证该对象的有效性。R允许自定义函数用来验证特定的类。我们可以通过setValidity函数来设定。

    > setValidity("TimeSeries",
    +   function(object){
    +     object@start <= object@end &&
    +     length(object@start) == 1 &&
    +     length(object@end) == 1
    +   }
    + )
    Class "TimeSeries" [in ".GlobalEnv"]
    
    Slots:
                                  
    Name:     data   start     end
    Class: numeric POSIXct POSIXct
    
    

    现在我们可以检查对象在validObject函数下是否有效。

    > validObject(my.TimeSeries)
    [1] TRUE
    

    之后我们新建TimeSeries对象时,R将会自动检查新对象的有效性,并通过抛出错误来拒绝错误的对象。

    (也可以在创建类的时候设定验证有效性的方法,详见setClass的完整定义)

    定义了类之后,我们来创建新的方法。时间序列有一个属性是周期。我们可以创建一个方法用来提取时间序列中的周期信息。

    > period.TimeSeries <- function(object) {
    +   if (length(object@data) > 1) {
    +     (object@end - object@start) / (length(object@data) - 1)
    +   } else {
    +     Inf
    +   }
    + }
    

    假如我们想创建一组函数用来从不同的对象中提取数据序列,而不用考虑对象的类型(即多态)。R提供了一种叫作泛型函数的机制可以实现。当我们对某个对象调用泛型函数时,R会基于该对象的类找到正确的方法去执行。我们创建一个函数来从泛型对象中提取数据序列:

    > series <- function(object) { object@data }
    > setGeneric("series")
    [1] "series"
    > series(my.TimeSeries)
    [1] 1 2 3 4 5 6
    

    调用setGeneric可以将series重定义为泛型函数,其默认的方法是旧的series函数的函数体:

    > series
    standardGeneric for "series" defined from package ".GlobalEnv"
    
    function (object) 
    standardGeneric("series")
    <environment: 0x205e930>
    Methods may be defined for arguments: object
    Use  showMethods("series")  for currently available ones.
    > showMethods("series")
    Function: series (package .GlobalEnv)
    object="ANY"
    object="TimeSeries"
        (inherited from: object="ANY")
    

    更进一步地,我们创建一个泛型函数来从对象中提取周期信息,并且特别指定它用来处理我们之前的创建的类。

    > period <- function(object) { object@period }
    > setGeneric("period")
    [1] "period"
    > setMethod(period, signature=c("TimeSeries"), definition=period.TimeSeries)
    [1] "period"
    attr(,"package")
    [1] ".GlobalEnv"
    > showMethods("period")
    Function: period (package .GlobalEnv)
    object="ANY"
    object="TimeSeries"
    

    调用泛型函数period可以计算TimeSeries对象:

    > period(my.TimeSeries)
    Time difference of 1 mins
    

    也可以对已存在的泛型函数定义自己的方法,比如为我们创建的类定义一个summary方法:

    > setMethod("summary",
    +    signature="TimeSeries",
    +    definition=function(object) {
    +      print(paste(object@start,
    +                     " to ",
    +                 object@end,
    +                 sep="", collapse=""))
    +      print(paste(object@data, sep="", collapse=","))
    +    }
    +  )
    [1] "summary"
    > summary(my.TimeSeries)
    [1] "2009-07-01 to 2009-07-01 00:05:00"
    [1] "1,2,3,4,5,6"
    

    甚至可以为一个已经存在的操作符定义新的方法:

    > setMethod("[",
    +   signature=c("TimeSeries"),
    +   definition=function(x, i, j, ..., drop) {
    +     x@data[i]
    +   }
    + )
    [1] "["
    > my.TimeSeries[3]
    [1] 3
    
    
    > my.TimeSeries # 查看my.TimeSeries对象
    An object of class "TimeSeries"
    Slot "data":
    [1] 1 2 3 4 5 6
    
    Slot "start":
    [1] "2009-07-01 GMT"
    
    Slot "end":
    [1] "2009-07-01 00:05:00 GMT"
    
    

    下次继续类实现的实例和S4、S3。


    学习整理自《R核心技术手册》

    相关文章

      网友评论

        本文标题:【r<-高级|理论】R-面向对象编程

        本文链接:https://www.haomeiwen.com/subject/bcmgsxtx.html