R語言:S4對象類型

前言linux

本文接上一篇文章 R語言基於S3的面向對象編程,本文繼續介紹R語言基於S4的面向對象編程。程序員

S4對象系統具備明顯的結構化特徵,更適合面向對象的程序設計。Bioconductor社區,以S4對象系統作爲基礎架構,只接受符合S4定義的R包。web

目錄編程

  1. S4對象介紹數據結構

  2. 建立S4對象架構

  3. 訪問對象的屬性函數

  4. S4的泛型函數工具

  5. 查看S4對象的函數this

  6. S4對象的使用spa

1 S4對象介紹

S4對象系統是一種標準的R語言面向對象實現方式,S4對象有明確的類定義,參數定義,參數檢查,繼承關係,實例化等的面向對象系統的特徵。

2 建立S4對象

本文的系統環境

  • Linux: Ubuntu Server 12.04.2 LTS 64bit

  • R: 3.0.1 x86_64-pc-linux-gnu

爲了方便咱們檢查對象的類型,引入pryr包做爲輔助工具。關於pryr包的介紹,請參考文章:[撬動R內核的高級工具包pryr](http://blog.fens.me/r-pryr/)

# 加載pryr包
> library(pryr)

2.1 如何建立S4對象?

因爲S4對象是標準的面向對象實現方式, 有專門的類定義函數 setClass() 和類的實例化函數new() ,咱們看一下setClass()和new()是如何動做的。

2.1.1 setClass()

查看setClass的函數定義

setClass(Class, representation, prototype, contains=character(),
          validity, access, where, version, sealed, package,
          S3methods = FALSE, slots)

參數列表:

  • Class: 定義類名

  • slots: 定義屬性和屬性類型

  • prototype: 定義屬性的默認值

  • contains=character(): 定義父類,繼承關係

  • validity: 定義屬性的類型檢查

  • where: 定義存儲空間

  • sealed: 若是設置TRUE,則同名類不能被再次定義

  • package: 定義所屬的包

  • S3methods: R3.0.0之後不建議使用

  • representation R3.0.0之後不建議使用

  • access R3.0.0之後不建議使用

  • version R3.0.0之後不建議使用

2.2 建立一個S4對象實例

# 定義一個S4對象
> setClass("Person",slots=list(name="character",age="numeric"))

# 實例化一個Person對象
> father<-new("Person",name="F",age=44)

# 查看father對象,有兩個屬性name和age
> father
An object of class "Person"
Slot "name":
[1] "F"

Slot "age":
[1] 44

# 查看father對象類型,爲Person
> class(father)
[1] "Person"
attr(,"package")
[1] ".GlobalEnv"

# 查看father對象爲S4的對象
> otype(father)
[1] "S4"

2.3 建立一個有繼承關係的S4對象

# 建立一個S4對象Person
> setClass("Person",slots=list(name="character",age="numeric"))

# 建立Person的子類
> setClass("Son",slots=list(father="Person",mother="Person"),contains="Person")

# 實例化Person對象
> father<-new("Person",name="F",age=44)
> mother<-new("Person",name="M",age=39)

# 實例化一個Son對象
> son<-new("Son",name="S",age=16,father=father,mother=mother)

# 查看son對象的name屬性
> son@name
[1] "S"

# 查看son對象的age屬性
> son@age
[1] 16

# 查看son對象的father屬性
> son@father
An object of class "Person"
Slot "name":
[1] "F"

Slot "age":
[1] 44

# 查看son對象的mother屬性
> slot(son,"mother")
An object of class "Person"
Slot "name":
[1] "M"

Slot "age":
[1] 39

# 檢查son類型
> otype(son)
[1] "S4"

# 檢查son@name屬性類型
> otype(son@name)
[1] "primitive"

# 檢查son@mother屬性類型
> otype(son@mother)
[1] "S4"

# 用isS4(),檢查S4對象的類型
> isS4(son)
[1] TRUE
> isS4(son@name)
[1] FALSE
> isS4(son@mother)
[1] TRUE

2.4 S4對象的默認值

> setClass("Person",slots=list(name="character",age="numeric"))

# 屬性age爲空
> a<-new("Person",name="a")
> a
An object of class "Person"
Slot "name":
[1] "a"

Slot "age":
numeric(0)

# 設置屬性age的默認值20
> setClass("Person",slots=list(name="character",age="numeric"),prototype = list(age = 20))

# 屬性age爲空
> b<-new("Person",name="b")

# 屬性age的默認值是20
> b
An object of class "Person"
Slot "name":
[1] "b"

Slot "age":
[1] 20

2.5 S4對象的類型檢查

> setClass("Person",slots=list(name="character",age="numeric"))

# 傳入錯誤的age類型
> bad<-new("Person",name="bad",age="abc")
Error in validObject(.Object) :
  invalid class 「Person」 object: invalid object for slot "age" in class "Person": got class "character", should be or extend class "numeric"

# 設置age的非負檢查
> setValidity("Person",function(object) {
+     if (object@age <= 0) stop("Age is negative.")
+ })
Class "Person" [in ".GlobalEnv"]

Slots:
Name:       name       age
Class: character   numeric

# 修傳入小於0的年齡
> bad2<-new("Person",name="bad",age=-1)
Error in validityMethod(object) : Age is negative.

2.6 從一個已經實例化的對象中建立新對象

S4對象,還支持從一個已經實例化的對象中建立新對象,建立時能夠覆蓋舊對象的值

> setClass("Person",slots=list(name="character",age="numeric"))

# 建立一個對象實例n1
> n1<-new("Person",name="n1",age=19);n1
An object of class "Person"
Slot "name":
[1] "n1"

Slot "age":
[1] 19

# 從實例n1中,建立實例n2,並修改name的屬性值
> n2<-initialize(n1,name="n2");n2
An object of class "Person"
Slot "name":
[1] "n2"

Slot "age":
[1] 19

3 訪問對象的屬性

在S3對象中,通常我使用$來訪問一個對象的屬性,但在S4對象中,咱們只能使用@來訪問一個對象的屬性

> setClass("Person",slots=list(name="character",age="numeric"))
> a<-new("Person",name="a")

# 訪問S4對象的屬性
> a@name
[1] "a"
> slot(a, "name")
[1] "a"

# 錯誤的屬性訪問
> a$name
Error in a$name : $ operator not defined for this S4 class
> a[1]
Error in a[1] : object of type 'S4' is not subsettable
> a[[1]]
Error in a[[1]] : this S4 class is not subsettable

4 S4的泛型函數

S4的泛型函數實現有別於S3的實現,S4分離了方法的定義和實現,如在其餘語言中咱們常說的接口和實現分離。經過setGeneric()來定義接口,經過setMethod()來定義現實類。這樣可讓S4對象系統,更符合面向對象的特徵。

普通函數的定義和調用

> work<-function(x) cat(x, "is working")
> work('Conan')
Conan is working

讓我來看看如何用R分離接口和現實

# 定義Person對象
> setClass("Person",slots=list(name="character",age="numeric"))

# 定義泛型函數work,即接口
> setGeneric("work",function(object) standardGeneric("work"))
[1] "work"

# 定義work的現實,並指定參數類型爲Person對象
> setMethod("work", signature(object = "Person"), function(object) cat(object@name , "is working") )
[1] "work"

# 建立一個Person對象a
> a<-new("Person",name="Conan",age=16)

# 把對象a傳入work函數
> work(a)
Conan is working

經過S4對象系統,把原來的函數定義和調用2步,爲成了4步進行:

  • 定義數據對象類型

  • 定義接口函數

  • 定義實現函數

  • 把數據對象以參數傳入到接口函數,執行實現函數

經過S4對象系統,是一個結構化的,完整的面向對象實現。

5 查看S4對象的函數

當咱們使用S4對象進行面向對象封裝後,咱們還須要能查看到S4對象的定義和函數定義。

還以上節中Person和work的例子

# 檢查work的類型
> ftype(work)
[1] "s4"      "generic"

# 直接查看work函數
> work
standardGeneric for "work" defined from package ".GlobalEnv"
function (object)
standardGeneric("work")
<environment: 0x2aa6b18>
Methods may be defined for arguments: object
Use  showMethods("work")  for currently available ones.

# 查看work函數的現實定義
> showMethods(work)
Function: work (package .GlobalEnv)
object="Person"

# 查看Person對象的work函數現實
> getMethod("work", "Person")
Method Definition:
function (object)
cat(object@name, "is working")
Signatures:
        object
target  "Person"
defined "Person"

> selectMethod("work", "Person")
Method Definition:
function (object)
cat(object@name, "is working")
Signatures:
        object
target  "Person"
defined "Person"

# 檢查Person對象有沒有work函數
>  existsMethod("work", "Person")
[1] TRUE
> hasMethod("work", "Person")
[1] TRUE

6 S4對象的使用

咱們接下用S4對象作一個例子,定義一組圖形函數的庫。

6.1 任務一:定義圖形庫的數據結構和計算函數

假設最Shape爲圖形的基類,包括圓形(Circle)和橢圓形(Ellipse),並計算出它們的面積(area)和周長(circum)。

  • 定義圖形庫的數據結構

  • 定義圓形的數據結構,並計算面積和周長

  • 定義橢圓形的數據結構,並計算面積和周長

如圖所示結構:

s4-shape1

定義基類Shape 和 圓形類Circle

# 定義基類Shape
> setClass("Shape",slots=list(name="character"))

# 定義圓形類Circle,繼承Shape,屬性radius默認值爲1
> setClass("Circle",contains="Shape",slots=list(radius="numeric"),prototype=list(radius = 1))

# 驗證radius屬性值要大等於0
> setValidity("Circle",function(object) {
+     if (object@radius <= 0) stop("Radius is negative.")
+ })
Class "Circle" [in ".GlobalEnv"]
Slots:
Name:     radius      name
Class:   numeric character
Extends: "Shape"

# 建立兩個圓形實例
> c1<-new("Circle",name="c1")
> c2<-new("Circle",name="c2",radius=5)

定義計算面積的接口和現實

# 計算面積泛型函數接口
> setGeneric("area",function(obj,...) standardGeneric("area"))
[1] "area"

# 計算面積的函數現實
> setMethod("area","Circle",function(obj,...){
+     print("Area Circle Method")
+     pi*obj@radius^2
+ })
[1] "area"

# 分別計算c1和c2的兩個圓形的面積
> area(c1)
[1] "Area Circle Method"
[1] 3.141593
> area(c2)
[1] "Area Circle Method"
[1] 78.53982

定義計算周長的接口和現實

# 計算周長泛型函數接口
> setGeneric("circum",function(obj,...) standardGeneric("circum"))
[1] "circum"

# 計算周長的函數現實
> setMethod("circum","Circle",function(obj,...){
+     2*pi*obj@radius
+ })

# 分別計算c1和c2的兩個圓形的面積
[1] "circum"
> circum(c1)
[1] 6.283185
> circum(c2)
[1] 31.41593

上面的代碼,咱們實現了圓形的定義,下來咱們實現橢圓形。

# 定義橢圓形的類,繼承Shape,radius參數默認值爲c(1,1),分別表示橢圓形的長半徑和短半徑
> setClass("Ellipse",contains="Shape",slots=list(radius="numeric"),prototype=list(radius=c(1,1)))

# 驗證radius參數
> setValidity("Ellipse",function(object) {
+     if (length(object@radius) != 2 ) stop("It's not Ellipse.")
+     if (length(which(object@radius<=0))>0) stop("Radius is negative.")
+ })
Class "Ellipse" [in ".GlobalEnv"]
Slots:
Name:     radius      name
Class:   numeric character
Extends: "Shape"

# 建立兩個橢圓形實例e1,e2
> e1<-new("Ellipse",name="e1")
> e2<-new("Ellipse",name="e2",radius=c(5,1))

# 計算橢圓形面積的函數現實
> setMethod("area", "Ellipse",function(obj,...){
+     print("Area Ellipse Method")
+     pi * prod(obj@radius)
+ })
[1] "area"

# 計算e1,e2兩個橢圓形的面積
> area(e1)
[1] "Area Ellipse Method"
[1] 3.141593
> area(e2)
[1] "Area Ellipse Method"
[1] 15.70796

# 計算橢圓形周長的函數現實
> setMethod("circum","Ellipse",function(obj,...){
+     cat("Ellipse Circum :\n")
+     2*pi*sqrt((obj@radius[1]^2+obj@radius[2]^2)/2)
+ })
[1] "circum"

# 計算e1,e2兩個橢圓形的周長
> circum(e1)
Ellipse Circum :
[1] 6.283185
> circum(e2)
Ellipse Circum :
[1] 22.65435

6.2 任務二:重構圓形和橢圓形的設計

上一步,咱們已經完成了 圓形和橢圓形 的數據結構定義,以及計算面積和周長的方法現實。不知你們有沒有發現,圓形是橢圓形的一個特例呢?

當橢圓形的長半徑和短半徑相等時,即radius的兩個值相等,造成的圖形爲圓形。利用這個特色,咱們就能夠從新設計 圓形和橢圓形 的關係。橢圓形是圓形的父類,而圓形是橢圓形的子類。

如圖所示結構:

s4-shape2

# 基類Shape
> setClass("Shape",slots=list(name="character",shape="character"))

# Ellipse繼承Shape
> setClass("Ellipse",contains="Shape",slots=list(radius="numeric"),prototype=list(radius=c(1,1),shape="Ellipse"))

# Circle繼承Ellipse
> setClass("Circle",contains="Ellipse",slots=list(radius="numeric"),prototype=list(radius = 1,shape="Circle"))

# 定義area接口
> setGeneric("area",function(obj,...) standardGeneric("area"))
[1] "area"

# 定義area的Ellipse實現
> setMethod("area","Ellipse",function(obj,...){
+     cat("Ellipse Area :\n")
+     pi * prod(obj@radius)
+ })
[1] "area"

# 定義area的Circle實現
> setMethod("area","Circle",function(obj,...){
+     cat("Circle Area :\n")
+     pi*obj@radius^2
+ })
[1] "area"

# 定義circum接口
> setGeneric("circum",function(obj,...) standardGeneric("circum"))
[1] "circum"

# 定義circum的Ellipse實現
> setMethod("circum","Ellipse",function(obj,...){
+     cat("Ellipse Circum :\n")
+     2*pi*sqrt((obj@radius[1]^2+obj@radius[2]^2)/2)
+ })
[1] "circum"

# 定義circum的Circle實現
> setMethod("circum","Circle",function(obj,...){
+     cat("Circle Circum :\n")
+     2*pi*obj@radius
+ })
[1] "circum"

# 建立實例
> e1<-new("Ellipse",name="e1",radius=c(2,5))
> c1<-new("Circle",name="c1",radius=2)

# 計算橢圓形的面積和周長
> area(e1)
Ellipse Area :
[1] 31.41593
> circum(e1)
Ellipse Circum :
[1] 23.92566

# 計算圓形的面積和周長
> area(c1)
Circle Area :
[1] 12.56637
> circum(c1)
Circle Circum :
[1] 12.56637

咱們重構後的結構,是否是會更合理呢!!

6.3 任務三:增長矩形的圖形處理

咱們的圖形庫,進一步擴充,須要加入矩形和正方形。

  • 定義矩形的數據結構,並計算面積和周長

  • 定義正方形的數據結構,並計算面積和周長

  • 正方形是矩形的特例,定義矩形是正方形的父類,而正方形是矩形的子類。

如圖所示結構:

s4-shape3

# 定義矩形Rectangle,繼承Shape
> setClass("Rectangle",contains="Shape",slots=list(edges="numeric"),prototype=list(edges=c(1,1),shape="Rectangle"))

# 定義正方形Square,繼承Rectangle
> setClass("Square",contains="Rectangle",slots=list(edges="numeric"),prototype=list(edges=1,shape="Square"))

# 定義area的Rectangle實現
> setMethod("area","Rectangle",function(obj,...){
+     cat("Rectangle Area :\n")
+     prod(obj@edges)
+ })
[1] "area"

# 定義area的Square實現
> setMethod("area","Square",function(obj,...){
+     cat("Square Area :\n")
+     obj@edges^2
+ })
[1] "area"

# 定義circum的Rectangle實現
> setMethod("circum","Rectangle",function(obj,...){
+     cat("Rectangle Circum :\n")
+     2*sum(obj@edges)
+ })
[1] "circum"

# 定義circum的Square實現
> setMethod("circum","Square",function(obj,...){
+     cat("Square Circum :\n")
+     4*obj@edges
+ })
[1] "circum"

# 建立實例
> r1<-new("Rectangle",name="r1",edges=c(2,5))
> s1<-new("Square",name="s1",edges=2)

# 計算矩形形的面積和周長
> area(r1)
Rectangle Area :
[1] 10
> area(s1)
Square Area :
[1] 4

# 計算正方形的面積和周長
> circum(r1)
Rectangle Circum :
[1] 14
> circum(s1)
Square Circum :
[1] 8

這樣,咱們的圖形庫,已經支持了4種圖形了!用面向對象的結構來設計,是否是結構化思路很清晰呢!!

6.4 任務四:在基類Shape中,增長shape屬性和getShape方法

接下來,要對圖形庫的全部圖形,定義圖形類型的變量shape,而後再提供一個getShape函數能夠檢查實例中的是shape變量。

這個需求,若是沒有面向對象的結構,那麼你須要在全部圖形定義的代碼中,都增長一個參數和一個判斷,若是有100圖形,改起來仍是挺複雜的。而面向對象的程序設計,就很是容易解決這個需求。咱們只須要在基類上改動代碼就能夠實現了。

如圖所示結構:

s4-shape4

# 從新定義基類Shape,增長shape屬性
> setClass("Shape",slots=list(name="character",shape="character"))

# 定義getShape接口
> setGeneric("getShape",function(obj,...) standardGeneric("getShape"))
[1] "getShape"

# 定義getShape實現
> setMethod("getShape","Shape",function(obj,...){
+     cat(obj@shape,"\n")
+ })
[1] "getShape"

其實,這樣改動一個就能夠了,咱們只須要重實例化每一個圖形的對象就好了。

# 實例化一個Square對象,並給shape屬性賦值
> s1<-new("Square",name="s1",edges=2, shape="Square")

# 調用基類的getShape()函數
> getShape(r1)
Rectangle

是否是很容易的呢!在代碼只在基類裏修改了,全部的圖形就有了對應的屬性和方法。

若是咱們再多作一步,能夠修改每一個對象的定義,增長shape屬性的默認值。

setClass("Ellipse",contains="Shape",slots=list(radius="numeric"),prototype=list(radius=c(1,1),shape="Ellipse"))
setClass("Circle",contains="Ellipse",slots=list(radius="numeric"),prototype=list(radius = 1,shape="Circle"))
setClass("Rectangle",contains="Shape",slots=list(edges="numeric"),prototype=list(edges=c(1,1),shape="Rectangle"))
setClass("Square",contains="Rectangle",slots=list(edges="numeric"),prototype=list(edges=1,shape="Square"))

再實例化對象時,屬性shape會被自動賦值

# 實例化一個Square對象
> s1<-new("Square",name="s1",edges=2)

# 調用基類的getShape()函數
> getShape(r1)
Rectangle

下面是完整的R語言的代碼實現:

setClass("Shape",slots=list(name="character",shape="character"))
setClass("Ellipse",contains="Shape",slots=list(radius="numeric"),prototype=list(radius=c(1,1),shape="Ellipse"))
setClass("Circle",contains="Ellipse",slots=list(radius="numeric"),prototype=list(radius = 1,shape="Circle"))
setClass("Rectangle",contains="Shape",slots=list(edges="numeric"),prototype=list(edges=c(1,1),shape="Rectangle"))
setClass("Square",contains="Rectangle",slots=list(edges="numeric"),prototype=list(edges=1,shape="Square"))

setGeneric("getShape",function(obj,...) standardGeneric("getShape"))
setMethod("getShape","Shape",function(obj,...){
  cat(obj@shape,"\n")
})


setGeneric("area",function(obj,...) standardGeneric("area"))
setMethod("area","Ellipse",function(obj,...){
  cat("Ellipse Area :\n")
  pi * prod(obj@radius)
})
setMethod("area","Circle",function(obj,...){
  cat("Circle Area :\n")
  pi*obj@radius^2
})
setMethod("area","Rectangle",function(obj,...){
  cat("Rectangle Area :\n")
  prod(obj@edges)
})
setMethod("area","Square",function(obj,...){
  cat("Square Area :\n")
  obj@edges^2
})


setGeneric("circum",function(obj,...) standardGeneric("circum"))
setMethod("circum","Ellipse",function(obj,...){
  cat("Ellipse Circum :\n")
  2*pi*sqrt((obj@radius[1]^2+obj@radius[2]^2)/2)
})
setMethod("circum","Circle",function(obj,...){
  cat("Circle Circum :\n")
  2*pi*obj@radius
})
setMethod("circum","Rectangle",function(obj,...){
  cat("Rectangle Circum :\n")
  2*sum(obj@edges)
})
setMethod("circum","Square",function(obj,...){
  cat("Square Circum :\n")
  4*obj@edges
})

e1<-new("Ellipse",name="e1",radius=c(2,5))
c1<-new("Circle",name="c1",radius=2)

r1<-new("Rectangle",name="r1",edges=c(2,5))
s1<-new("Square",name="s1",edges=2)

area(e1)
area(c1)
circum(e1)
circum(c1)

area(r1)
area(s1)
circum(r1)
circum(s1)

經過這個例子,咱們全面地瞭解了R語言中面向對象的使用,和S4對象系統的面向對象程序設計!

在程序員的世界裏,世間萬物均可以抽象成對象。

相關文章
相關標籤/搜索