您的位置:首页 > 运维架构

R语言OOP(3):S4的实现方法

2014-04-23 16:22 169 查看
搁置了一些时日,原本写好的提纲很多都忘了要写些什么内容了。一些标题先留空,以后有时间再补充吧。

1 闲话

《R Language Definition》中只有S3 OOP的介绍,找不到S4 OOP的相关说明,因为S4 OOP还不够完善,没有定型。但很多人已经大量使用它了,尤其是BioC们,前些年用S3写的一些包都在逐渐更新为S4系统,他们可能是尝到了某些甜头。不过苦头肯定也不少,R core们宣布自R 3.0版后弃用类定义setClass函数的representation参数,改用slots和contains参数。BioC们又有得折腾了。

话虽如此,好的系统还是很有生命力的,能吸引用户。同样是序列处理的软件包,seqinr出现的时间也不短了,没用S4,和BioC格格不入,功能虽然很齐全但响应者似乎寥寥。我看seqinr说明书的第一感觉就一个字:“乱”,心情也是一个字:“烦”。不过应该肯定的是seqinr还是非常不错的,小巧、函数间的依赖关系简单、容易移植,适合小量序列的分析。闲话少说,入题。

2 S4类

2.1 S4类定义

S4 OOP系统有比较完善的CLASS定义方法,用于类定义的函数是setClass。

## NOT RUN (非运行代码)
setClass(Class, representation, prototype, contains = character(), validity,
access, where, version, sealed, package, S3methods = FALSE, slots)


2.1.1 最基本的设置:Class和slots

先看例子再解释:

Somebody <- setClass(Class = "Somebody", slots = c(name = "character", gender = "factor"))
someone <- Somebody(name = "Adam", gender = factor("M"))
someone

## An object of class "Somebody"
## Slot "name":
## [1] "Adam"
##
## Slot "gender":
## [1] M
## Levels: M


上面第一行代码定义了一个类的最基本两个内容:类名称和类对象存储的数据。参数说明如下:

Class 用于表示类名称的字符串

slots 很多人称它为“接口”,用于存储对象的具体数据。既然是数据就得有名称和数据类型,slots的作用就是指定这两者。它的内容是有名的列表或有名向量,列表或向量的名称(如上面的name和gender)表示类数据的名称,而它们的值(字符串)表示数据的类型(如上面的character和factor)。

setClass函数结果可以不赋值,但赋值运算会在创建类的同时获得一个类构造函数。构造函数的名称一般和类名称相同,但这不是硬性规定:

diets <- setClass(Class = "Diet", slots = c(food = "character", drink = "character"))
diets(food = "rice", drink = "water")

## An object of class "Diet"
## Slot "food":
## [1] "rice"
##
## Slot "drink":
## [1] "water"


如果setClass函数结果不赋值,构造函数不会产生,如果要产生此类对象就得使用系统的new函数了。

setClass(Class = "Anybody", slots = c(name = "character", gender = "factor"))


## 错,类定义并没有赋值,没有产生Anybody构造函数
Anybody(name = "Adam", gender = factor("M"))

## Error: 没有"Anybody"这个函数

new(Class = "Anybody", name = "Adam", gender = factor("M"))

## An object of class "Anybody"
## Slot "name":
## [1] "Adam"
##
## Slot "gender":
## [1] M
## Levels: M

## setClass赋值的作用事实上就是产生了一个调用new函数的函数:
Somebody

## class generator function for class "Somebody" from package '.GlobalEnv'
## function (...)
## new("Somebody", ...)


从R语言的根本上来看(不涉及任何OOP概念),slots和CLASS一样,都是数据的属性而已,只是换了个名称:

str(attributes(someone))

## List of 3
##  $ name  : chr "Adam"
##  $ gender: Factor w/ 1 level "M": 1
##  $ class : atomic [1:1] Somebody
##   ..- attr(*, "package")= chr ".GlobalEnv"


2.1.2 类继承:contains

contains 用于设置新类中包含(即继承)的其他已定义类(父类),其作用是把父类中的slots包含进来:

showClass("Somebody")

## Class "Somebody" [in ".GlobalEnv"]
##
## Slots:
##
## Name:       name    gender
## Class: character    factor

Anybody <- setClass("Anybody", contains = "Somebody", slots = c(skin = "character"))
showClass("Anybody")

## Class "Anybody" [in ".GlobalEnv"]
##
## Slots:
##
## Name:       skin      name    gender
## Class: character character    factor
##
## Extends: "Somebody"


contains可以包含多个类,继承多个父类的slots:

Anybody <- setClass("Anybody", contains = c("Somebody", "Diet"), slots = c(skin = "character"))
showClass("Anybody")

## Class "Anybody" [in ".GlobalEnv"]
##
## Slots:
##
## Name:       skin      name    gender      food     drink
## Class: character character    factor character character
##
## Extends: "Somebody", "Diet"


2.1.3 原型设置:prototype

prototype 原型,即类对象初始化(新建对象)时slots中的默认数据,相当于函数的默认参数。这是一个有名列表,不能用有名向量。

Somebody <- setClass(Class = "Somebody", slots = c(name = "character", gender = "factor"),
prototype = list(name = "Adam", gender = factor("M")))
str(Somebody())

## Formal class 'Somebody' [package ".GlobalEnv"] with 2 slots
##   ..@ name  : chr "Adam"
##   ..@ gender: Factor w/ 1 level "M": 1

diets <- setClass(Class = "Diet", slots = c(food = "character", drink = "character"),
prototype = list(food = "rice", drink = "water"))
str(diets())

## Formal class 'Diet' [package ".GlobalEnv"] with 2 slots
##   ..@ food : chr "rice"
##   ..@ drink: chr "water"


创建新类时如果使用contains,新类的构造函数将自动父类的原型设置

Anybody <- setClass("Anybody", contains = c("Somebody", "Diet"), slots = c(skin = "character"),
prototype = list(skin = character()))
str(Anybody())

## Formal class 'Anybody' [package ".GlobalEnv"] with 5 slots
##   ..@ skin  : chr(0)
##   ..@ name  : chr "Adam"
##   ..@ gender: Factor w/ 1 level "M": 1
##   ..@ food  : chr "rice"
##   ..@ drink : chr "water"


2.1.4 类型检查:validity

定义S4新类时本身就已经具备类型检查的能力,如果给对象提供的数据类型不正确就直接出错而不是警告:

## 下面的skin不是字符型,不能通过类型检查
Anybody(skin = 1)

## Error: invalid class "Anybody" object: invalid object for slot "skin" in
## class "Anybody": got class "numeric", should be or extend class
## "character"


然而,这种检查能力还是相当有限的,编程过程中往往需要对数据进行更严格的审查,比如对数据进行进行数量和取值的限定。这可以通过validity参数设置类型检查函数来实现。

Anybody <- setClass("Anybody", contains = c("Somebody", "Diet"), slots = c(skin = "character"),
prototype = list(skin = character()), validity = function(object) {
if (length(object@skin) != 1)
return("\"skin\" must be length of 1.")
if (!object@skin %in% c("W", "Y", "B"))
return("Invalid \"skin\" type.")
return(TRUE)
})


## 正确设置可以通过类型检查
(xx <- Anybody(skin = "Y"))

## An object of class "Anybody"
## Slot "skin":
## [1] "Y"
##
## Slot "name":
## [1] "Adam"
##
## Slot "gender":
## [1] M
## Levels: M
##
## Slot "food":
## [1] "rice"
##
## Slot "drink":
## [1] "water"

## validity设置了skin只能是‘W’,‘Y’,‘B’中的一个,设置其他值或长度不为1都出错
Anybody(skin = c("Y", "W"))

## Error: invalid class "Anybody" object: "skin" must be length of 1.

Anybody(skin = "Black")

## Error: invalid class "Anybody" object: Invalid "skin" type.


2.1.5 setClass函数的其他参数

除以上参数外,类定义函数setClass还可以设置的参数有:

where 用于指定类定义存储的环境(命名空间),如果在软件包里面定义类,类默认存储到软件包的命名空间

package 此参数极少使用,作用和where类似

sealed 是否封装(TRUE/FALSE)。如果设为TRUE,已经用setClass定义过的类(名称)就不能用setClass再定义,防止误操作

下面的参数在R 3.0.0版以后都被弃用了,虽仍可用但应尽量避免:

S3methods

representation

access

version

2.2 通过读取数据产生S4类对象

这是考虑用户层次和使用体验的内容,对R软件开发者来说也相当简单:编写几个函数,通过读取不同类型的文件返回S4类对象。一般软件包都会替用户考虑这点很起码的要求。比如Affy包,用ReadAffy函数就获得了AffyBatch类对象,不用关心AffyBatch类是个什么东西。很多人可能根本就不想知道S3或S4是什么玩意,伤脑筋。

3 虚拟类

4 类联合

5 S4泛型函数和方法

5.1 定义的一般过程

S4的泛型函数通过调用setGeneric函数产生,该函数的用法如下:

## NOT RUN
setGeneric(name, def = , group = list(), valueClass = character(), where = ,
package = , signature = , useAsDefault = , genericFunction = , simpleInheritanceOnly = )


参数虽然很多,但常用两个:

name: 表示泛型函数名称的字符串。这个函数必需已经定义,它将被转成泛型函数(如果它还不是泛型函数),而且该函数将被设为默认方法

def: 这是可选项。如果name参数没有对应的已有函数,这一项必需提供。如果name已经有对应的函数,使用def项可以指定其他的函数作为泛型函数(偷梁换柱)。

泛型函数的方法使用setMethod函数进行定义:

## NOT RUN
setMethod(f, signature = character(), definition, where = topenv(parent.frame()),
valueClass = NULL, sealed = FALSE)


f:泛型函数名称

signature:识别指纹,即对象的类名称

在R终端输入变量名后回车会得到变量的内容,其实质是调用show函数显示变量。下面通过设置泛型函数及其方法重定义show函数输出Anybody类对象的内容:

## 重定义前show函数输出的内容
xx

## An object of class "Anybody"
## Slot "skin":
## [1] "Y"
##
## Slot "name":
## [1] "Adam"
##
## Slot "gender":
## [1] M
## Levels: M
##
## Slot "food":
## [1] "rice"
##
## Slot "drink":
## [1] "water"

setGeneric(name = "show")

## [1] "show"

setMethod("show", signature = "Anybody", function(object) show(object@drink))

## [1] "show"

## 重定义后show函数输出的内容
xx

## [1] "water"


5.2 设置获取类对象内容的辅助函数

5.3 设置S4类对象赋值(内容替换)方法

5.4 类转换

用于S4类转换的函数是as。如果定义新类时使用了contains获得了继承关系,那么就可以用as函数将一个对象强制转换为其父类对象,对象的内容是父类定应的相应部分内容:

class(xx)

## [1] "Anybody"
## attr(,"package")
## [1] ".GlobalEnv"

as(xx, "Diet")

## An object of class "Diet"
## Slot "food":
## [1] "rice"
##
## Slot "drink":
## [1] "water"

as(xx, "Somebody")

## An object of class "Somebody"
## Slot "name":
## [1] "Adam"
##
## Slot "gender":
## [1] M
## Levels: M


Author: ZGUANG@LZU

Created: 2014-03-04 二 13:42

Emacs 24.3.1 (Org mode 8.2.1)

Validate
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: