【R】R语言基于S4的面向对象编程(Part1)
(2016-05-31 16:23:06)
标签:
r面向对象s4 |
分类: R学习笔记 |
前言
S4对象系统具有明显的结构化特征,更适合面向对象的程序设计。Bioconductor社区,以S4对象系统做为基础架构,只接受符合S4定义的R包。
要点提示:
setClass:定义一个类,一个类可以有多个函数
setGeneric:定义一个函数的接口(类似于变量使用前的申明,或C语言中的函数原型),同一个函数接口可以有多个具体实现,且这些具体实现可以属于不同的类
setMethod:在setGeneric中定义的函数的具体实现,属于某一个特定的类
目录
1.
2.
3.
4.
5.
6.
1.
S4对象介绍
S4对象系统是一种标准的R语言面向对象实现方式,S4对象有明确的类定义,参数定义,参数检查,继承关系,实例化等的面向对象系统的特征。
2.
创建S4对象
本文的系统环境
²Linux: Ubuntu Server 12.04.2 LTS 64bit
²R: 3.0.1 x86_64-pc-linux-gnu
转载这篇文章时,我用的系统环境:
²Windows 7
²R: 3.3.0
为了方便我们检查对象的类型,引入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)
参数列表:
·
·
·
·
·
·
·
·
·
·
·
·
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"),
# 实例化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) :
# 设置age的非负检查
> setValidity("Person",function(object) {
+
+ })
Class "Person" [in ".GlobalEnv"]
Slots:
Name:
Class:
character
# 修传入小于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,即接口
# 创建了一个泛型函数work,但并没有与Person关联起来
> setGeneric("work",function(object) standardGeneric("work"))
[1] "work"
# 定义work的现实,并指定参数类型为Person对象
# 在这里与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"
# 直接查看work函数
> work
standardGeneric for "work" defined from package ".GlobalEnv"
function (object)
standardGeneric("work")
Methods may be defined for arguments: object
Use
# 查看work函数的现实定义
> showMethods(work)
Function: work (package .GlobalEnv)
object="Person"
# 查看Person对象的work函数现实
> getMethod("work", "Person")
Method Definition:
function (object)
cat(object@name, "is working")
Signatures:
target
defined "Person"
> selectMethod("work", "Person")
Method Definition:
function (object)
cat(object@name, "is working")
Signatures:
target
defined "Person"
# 检查Person对象有没有work函数
>
[1] TRUE
> hasMethod("work", "Person")
[1] TRUE