淺談R語言的面向對象編程

R語言有兩種不一樣的OOP機制,分別是從其前身S語言繼承而來的S3 Object和S4 Object,其中S4 Object更加的正式、也是如今用於開發的主力軍,因此本文就從S4 Object談起,並在最後討論一下古老的S3 Object。
shell

那咱們就開始吧!首先咱們來設計一個時間序列類,在它的內部,須要包含主數據、起始時間與截止時間、取樣間隔這些數據。在R中咱們能夠定義以下:編程

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

在這段代碼中,data/start/end用於存放數據,稱做「槽(slot)」。函數

如今咱們已經定義了一個類,咱們就來建立一個TimeSeries對象吧!this

    My_TimeSeries <- new("TimeSeries",
        data = c(1,2,3,4,5,6),
        start = as.POSIXct("01/12/2015 0:00:00", tz = "GMT",
        format = "%m/%d/%Y %H:%M:%S"),
        end = as.POSIXct("12/04/2015 0:00:00", tz = "GMT",
        format = "%m/%d/%Y %H:%M:%S")
    )

與其餘OOP語言相似,R中新建對象的通用函數也叫」new」,但只能用於新建S4對象。如今咱們來看看咱們剛剛新建的My_TimeSeries:spa

    > My_TimeSeries
    An object of class "TimeSeries"
    Slot "data":
    [1] 1 2 3 4 5 6
    Slot "start":
    [1] "2015-01-12 GMT"
    Slot "end":
    [1] "2015-12-04 GMT"

    >My_TimeSeries@data   #可使用"@"符號來引用類中的內容
    [1] 1 2 3 4 5 6
    > My_TimeSeries@start
    [1] "2015-01-12 GMT"

可是,一個bug出現了:若是用戶把start和end顛倒、或者把end誤輸爲一個比start還靠前的時間,這樣會形成時間序列變得沒有意義。R語言提供了一個新建對象時的檢驗機制,只須要在setValidity函數中設置一下:prototype

    setValidity("TimeSeries",
        function(object) {
            object@start < object@end &&
            length(object@start) == 1 &&
            length(object@end) == 1
        }
    )

如今咱們來試一下定義一個end在start前的TimeSeries對象:設計

    > bad_TimeSeries <- new("TimeSeries",
    + data=c(7, 8, 9, 10, 11, 12),
    + start=as.POSIXct("07/01/2009 0:06:00", tz="GMT",
    + format="%m/%d/%Y %H:%M:%S"),
    + end=as.POSIXct("07/01/1999 0:11:00", tz="GMT",
    + format="%m/%d/%Y %H:%M:%S")
    + )
    Error in validObject(.Object) : 類別爲「TimeSeries」的對象不對: FALSE

同時,也可使用validObject()函數來檢驗一個對象是否有效。code

    > validObject(My_TimeSeries)
    [1] TRUE

其實,在定義類的時候也能夠經過validity參數定義該類的合法性判斷,如:orm

    setClass("anotherTimeSeries",
        representation(
            data = "numeric",
            start = "POSIXct",
            end = "POSIXct"
        ),
        validity = function(object){ #定義時加上合法性判斷
            object@start < object@end &&
            length(object@start) == 1 &&
            length(object@end) == 1
        } 
    )

這樣定義與前面的先定義類、後定義合法性檢測的作法是等價的,只是把兩步都集成到了setClass()函數中。對象

下面咱們來看一下R語言中函數的多態性。咱們先從重載一個通用函數summary()開始:

    > summary(My_TimeSeries)
    Length Class Mode 
    1 TimeSeries S4 
    > setMethod("summary", #重載summary
    +     signature = "TimeSeries",
    +     definition = function(object){
    +         print( paste(object@start, " to ", object@end,
    +                     sep = "", collapse = ""))
    +         print( paste(object@data, sep = ";", collapse = ""))
    +     }
    + )
    從the global environment裏的程輯包‘base’爲‘summary’創建新的泛型函數
    [1] "summary"
    > summary(My_TimeSeries)
    [1] "2015-01-12 to 2015-12-04"
    [1] "123456"

咱們能夠看到,當咱們沒對TimeSeries類重載summary()函數的時候,summary(My_TimeSeries)只提供了一些簡要的信息。而在咱們重載後,它就能夠按照咱們的要求輸出信息了。

同時,咱們知道運算符在R中也是至關於函數調用,也就是a+b‘+’(a,b)是等價的。經過這個特性咱們就能夠重載R語言的運算符。

    #這樣,就可使用My_TimeSeries[i]了
    setMethod("[",
        signature("TimeSeries"),
        definition = function(x, i, j, ..., drop){
            return(x@data[i])
        }
    )

    > My_TimeSeries[3]
    [1] 3

而要新建一個泛型函數,則可使用setGeneric()函數來定義、再用setMethod()函數來實現它對各類類的功能。

    setGeneric("increment",       #創建一個函數名爲increment的泛型函數
        def = function(object, step, ...)
        standardGeneric("increment")
    )
    #對TimeSeries類重載increment函數,使之返回object[step+1]-object[1]的數值
    setMethod("increment",        
        signature = "TimeSeries",
        def = function(object, step, ...){
            return(object[step+1] - object[1])
        }
    )
    #對numeric類重載increment函數,使之返回object[step-1]-object[1]的數值
    setMethod("increment",
        signature = "numeric",
        def = function(object, step, ...){
            return(object[step-1] - object[1])
        }
    )

那麼咱們的定義的這個increment()泛型函數是否有效呢?咱們來檢驗一下:

   > increment(My_TimeSeries,3)#根據咱們的定義,應當返回My_TimeSeries[4]-My_TimeSeries[1]
   [1] 3
   > vec <- vector("numeric", length = 6)
   > vec <- c(1:6)    
   #如今咱們來看一下對numeric型向量,運行increment()函數的結果
   > increment(vec, 3)
   [1] 1

在泛型函數的最後,讓我來寫一個錯誤的示範:

    > setMethod("anotherIncrement",
    +     signature = "TimeSeries",
    +     def = function(object, step, ...){
    +         return(object[step+1] - object[1])
    +     }
    + )
    Error in setMethod("anotherIncrement", signature = "TimeSeries", def = function(obj    ect, : 
    函數‘anotherIncrement’沒有定義

在這個例子中,因爲我沒有定義anotherIncrement()爲泛型函數,直接調用setMethod()就會報錯——由於你根本沒有定義它!

而若是咱們對某個S4泛型函數不太瞭解,不知道它能夠用於哪些類時,就可使用showMethods()函數來看獲得它能夠做用的對象。

    > showMethods(increment)
    Function: increment (package .GlobalEnv)
    object="integer"
        (inherited from: object="numeric")
    object="numeric"
    object="TimeSeries"

下面咱們來看看類的派生:如今咱們想要一個類來記錄我的體重的變化狀況。咱們但願記錄下我的的姓名和身高,其餘的信息直接使用TimeSeries類記錄就能夠了,咱們能夠定義以下:

    setClass("WeightHistory", #派生
        representation(
            height = "numeric",
            name = "character"
        ),
        contains = "TimeSeries"
    )

如今咱們來建立一個WeightHistory類的對象,來儲存AlexDannel的體重數據

    AlexDannel <- new("WeightHistory",
        data = c(120,118,119,123,121,119),
        start = as.POSIXct("07/01/2015 0:00:00", tz = "GMT",
        format = "%m/%d/%Y %H:%M:%S"),
        end = as.POSIXct("12/01/2015 0:00:00", tz = "GMT",
        format = "%m/%d/%Y %H:%M:%S"),
        height = 166,
        name = "Alex Dannel"
    )

有沒有和新建TimeSeries序列對象的時候很像呢?

咱們還能夠用另外一種方法定義WeightHistory類,那就是先定義一個Person類,裏面包含name和height的slot(槽),而後直接從Person類和TimeSeries類繼承出來。

    setClass("Person",
        representation(
            height = "numeric",
            name = "character"
        ) 
    )

   setClass("anotherWeightHistory",
       contains = c("TimeSeries", "Person")
   )

虛類:差點忘了還有虛類這個東東~ 其實在R中定義虛類也特別簡單

    setClass("Cat",   #定義一個cat類,讓NamedThing做爲它和Person的虛基類
        representation(
            breed = "character",
            name = "character"
        )
    )

    setClassUnion("NamedThing",    #定義虛基類
        c("Person", "Cat")
    )

下面咱們來簡單討論一下S3 Object類吧!其實S3類要比S4類更加「隨意」,而S3類與JavaScript這種基於原型的(prototype-based)類很是類似。

#在S3類中,早已有對TimeSeries的定義,ts類對如今的R也是可用,如今咱們來建立一個ts對象
my.ts <- ts(data=c(1, 2, 3, 4, 5), start=c(2009, 2), frequency=12)

須要注意的是,S3類中不能使用@來取slot中的值。

而要建立一個S3類的對象,則可使用attr()函數或者structure()函數:

    > x<-1    #經過attr建立
    > attr(x,'class')<-'foo'    > x
    [1] 1
    >attr(,"class")
    [1] "foo"
    >class(x)
    "foo"
    >otype(x)    #檢查x的類型
    "S3"
#經過structure()建立
> y <- structure(2, class = "foo")
> y
[1] 2
attr(,"class")
[1] "foo"
> class(y)
[1] "foo"
> otype(y)
[1] "S3"

而要定義一個S3泛型函數,也是比較靈活的——只需以下三步:

1. Pick a name for the generic function. We’ll call this gname.
2. Create a function named gname. In the body for gname, call UseMethod(「gname「).
3. For each class that you want to use with gname, create a function called
    gname.classname whose first argument is an object of class classname.

——《R in a nutshell》, 2nd Edition

以plot爲例,咱們想要重載plot函數,使之能夠對TimeSeries類繪圖,就能夠這樣定義:

    plot.TimeSeries <- function(object, ...){
        plot(object@data, ...)
    }

如今,你就能夠直接經過plot(My_TimeSeries)來畫出圖像了!

而若是想要查看S3泛型函數能夠用於哪些類時,就可使用methods()函數來看獲得它能夠做用的對象(由於用S4的showMethods()函數會報錯→_→)。

> methods(plot)
 [1] plot.acf* plot.data.frame* plot.decomposed.ts* plot.default plot.dendrogram* plot.density* 
 [7] plot.ecdf plot.factor* plot.formula* plot.function plot.hclust* plot.histogram* 
[13] plot.HoltWinters* plot.isoreg* plot.lm* plot.medpolish* plot.mlm* plot.ppr* 
[19] plot.prcomp* plot.princomp* plot.profile.nls* plot.raster* plot.shingle* plot.spec* 
[25] plot.stepfun plot.stl* plot.table* plot.trellis* plot.ts plot.tskernel* 
[31] plot.TukeyHSD* 
see '?methods' for accessing help and source code

咱們甚至能夠經過gets3method()函數來查看S3泛型函數的源代碼:

library(lattice)
getS3method("histogram", class = "formula")

 

到這裏,筆者所知的R語言面向對象編程就介紹完畢了。因爲做者水平有限,許多系統函數的參數沒能系統的描述。讀者不妨仔細閱讀setClass、setGeneric、setMethod、new、method等函數的幫助頁面,以加深對R語言OOP的理解。

相關文章
相關標籤/搜索