面向对象编程这个概念自从java推出以来就是速度成为一种流行,以至于我们在写c++代码时不得不时刻想象着如何从structure转象class。而对于R来说,也是一样的。R现在主要有两种内置的OOP系统,还有许多扩展包推出。所谓的S3系统(Chambers和Hastie在1992年提出)和S4系统(Chambers于1998年对S3进行了改进,以期提高可靠性及易用性)。S3系统当然也是易用性很高的,但是它的可靠性比较差,而且命名规则严格,不适合于复杂软件的开发。而S4系统正是为了发进这些缺点而开发出来的。
Freidman在2001年总结了OOP的四个重要概念:
- 对象objects encapsulate state information and control behavior
- 类class describe general properties for groups of objects
- 继承inheritance new classes can be defined in terms of existing classes
- 多态polymorphism a (generic) function has different behaviors, although similar outputs, depending on the class of one or more of its arguments.
在这里,我就不讲S3 OOP了。
S4 OOP
一个简单的例子,我们要建立一个类,类名为PWM,它里面会有pwm, consensus, ic, width以及alphabet等属性,它会有四个方法:plot, summary, print, show。
首先设置类名及其属性,使用methods::setClass
setClass("pwm", representation( pwm="matrix", consensus="character", ic="numeric", width="numeric", alphabet="character")) |
然后我们需要设置它的方法。因为方法是多态的,所以在使用的时候,最好首先使用methods::setGeneric方法来进行dispatch。使用dispatch的原因是对于相同的一个方法名,可能会指向多种不同的类,具体使用哪一个来进行计算,需要让计算机对此有个判断。比如summary方法,它就是一个很普通的名字,你写的很多类都要用到,当我们直接使用summary(pwm)时,它就需要使用对于summary方法,是可以应用在PWM类上的。而对于plot和show方法,在global environment当中已经有它是generic function的记录了,所以不需要再设置一个新的了。当然,如果你需要覆盖以及的generic function记录,当然也是可以的。
setGeneric("summary", function(object) standardGeneric("summary")) setGeneric("print", function(x) standardGeneric("print")) |
接下来就是写具体的方法了。使用methods::setMethod方法
setMethod("plot", signature(x="pwm"), function(x, y="missing",...) { seqLogo(x) }) setMethod("summary", signature(object="pwm"), function(object,...){ cat("Position weight matrix:\n") print(round(object@pwm,4)) cat("\n\nInformation content:\n") print(round(object@ic,4)) cat("\n\nConsensus sequence:\n") print(object@consensus) }) setMethod("print", signature(x="pwm"), function(x,...){ print(round(x@pwm,4)) }) setMethod("show", "pwm", function(object){ show(round(object@pwm,4)) }) |
我们可以使用methods::getClass方法来获取PWM类的情况,也可以使用methods::getSlots方法来显示pwm属性的资料。
getClass("pwm") getSlots("pwm") |
对于类而言,一个重要的我特性就是可以继承。在R中,类的继承是在类申明时使用contains来传递。
> setClass("pwmcolorspace",representation(color="list"), contains="pwm") > getSlots("pwmcolorspace") color pwm consensus ic width alphabet "list" "matrix" "character" "numeric" "numeric" "character" > slotNames("pwmcolorspace") [1] "color" "pwm" "consensus" "ic" "width" "alphabet" |
那么,如果需要设置一个属性的初始值以及验证赋值是否有效应该如何处理呢?可以在类申明时使用prototype以及validity参数来传递。
> setClass("DNA",representation(seq="character",name="character"), + prototype=prototype(seq="ACGT"), + validity=function(object){ + if(grepl("^[ACGTN]+$",object@seq)) TRUE + else "seq must be a string with ACGTN"}) > dna<-new("DNA") > dna An object of class "DNA" Slot "seq": [1] "ACGT" Slot "name": character(0) > dna<-new("DNA",seq="give an error back") Error in validObject(.Object) : invalid class “DNA” object: seq must be a string with ACGTN |
validity也可以通过setValidity方法来实现
> removeClass("DNA") [1] TRUE > setClass("DNA",representation(seq="character",name="character"), + prototype=prototype(seq="ACGT")) > setValidity("DNA",function(object){ + if(grepl("^[ACGTN]+$",object@seq)) TRUE + else "seq must be a string with ACGTN"}) > dna<-new("DNA",seq="generate an error") Error in validObject(.Object) : invalid class “DNA” object: seq must be a string with ACGTN |
而prototype也可以通过initialise来实现
> removeClass("DNA") [1] TRUE > setClass("DNA",representation(seq="character",name="character")) > setMethod("initialize","DNA",function(.Object, ...){.Object@seq="ACTGN"; .Object}) [1] "initialize" > new("DNA") An object of class "DNA" Slot "seq": [1] "ACTGN" Slot "name": character(0) |
类型转换对于R来说也是非常重要的。可以通过setAs来定义
> setAs(from="DNA", to="character", function(from){ + from@seq + }) > dna<-new("DNA") > as(dna,"character") [1] "ACTGN" |
从上面的书写过程当中,我们已经体会到如何来访问一个类当中的属性了,那就是使用@符号。其实还可以通attr方法来实现
> as(dna,"character") [1] "ACTGN" > attr(dna,"seq") [1] "ACTGN" > attr(dna,"seq")<-"AATTCCGGT" > attr(dna,"seq") [1] "AATTCCGGT" |