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的理解。