面向对象是一种对现实世界理解和抽象的方法,当代码复杂度增加难以维护的时候,面向对象就会显得非常重要。学过Java和Javascript两种语言的话,不难理解面向对象。
什么是面向对象?
面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。早期的计算机编程是基于面向过程的方法,例如实现算术运算2 3 4=9,通过设计一个算法就可以解决当时的问题。
随着计算机技术的不断提高,计算机被用于解决越来越复杂的问题。一切事物皆对象,通过面向对象的方式,将现实世界的事物抽象成对象,现实世界中的关系抽象成类、继承,帮助人们实现对现实世界的抽象与数字建模。通过面向对象的方法,更利于用人理解的方式对复杂系统进行分析、设计与编程。同时,面向对象能有效提高编程的效率,通过封装技术,消息机制可以像搭积木的一样快速开发出一个全新的系统。面向对象是指一种程序设计范型,同时也是一种程序开发的方法。对象指的是类的集合。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。
面向对象的3个特征:封装,继承,多态
封装:是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
继承:子类自动共享父类数据结构和方法的机制,这是类之间的一种关系。在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。通过继承创建的新类称为“子类”或“派生类”;被继承的类称为“基类”、“父类”或“超类”。
多态: 指由继承而产生的相关的不同的类,其对象对同一消息会做出不同的响应。
R为什么要进行面向对象编程?
R主要面向统计计算,而且代码量一般不会很大,几十行,几百行,使用面向过程的编程方法就可以很好地完成编程的任务。
不过,虽然R语言的持续手热,伴随着越来越多的工程背景的人的加入,R语言开始向更多的领域发展。原来的少量的代码的面向过程的编码方式,会越来越难以维护海量代码的项目,所以必须有一种新的编程方式来代码原来的面向过程的编码思路,这种新的编程方式就是面向对象编程(Object Oriented Programming, OOP)。
面向对象编程,早在C /Java时代就被广泛使用了,几乎90%以上的Java框架都是按面向对象的方法设计的;8年前Javascript各种面向过程编码让前端开发困难重重,直到Google的Gmail的Web端出现,才让大家认识到原来Javascript也可以面向对象编程,随后的jQuery, ExtJS等类库的完全面向对象的实现,终于让Javascript承得起前端的天空,后来的Node的诞生更是让Javascript拓宽了应用领域。
当R语言被大家所看好的同时,我们也要开始思考,如何才能让R成为工业界的开发语言?应用如何构建非统计计算的项目?如何用R有效的编写10万行以上的代码?
我想这个答案就是以面向对象进行编程,现在的R就像8年前的Javascript,需要大公司和牛人来推动。从我的观察来看,以Hadley Wickham为代表的R语言领军人物,已经开始在R包中全面引入面向对象思路进行R包的开发了。
R的面向对象编程
R语言的类有S3类和S4类,S3类用的比较广,创建简单粗糙但是灵活,而S4类比较精细,具有跟C 一样严格的结构。大多数R对象都是基于S3类(来源于第三代S语言),例如直方图函数hist()输出是一个包含多个组件的列表,它还有一个属性(attribute),用来指定列表的类,即histogram类。R的面向对象编程是基于泛型函数(generic function)的,而不是基于类层次结构。
类用在泛型函数中,泛型函数是一个函数族,其中的每个函数都有相似的功能,但是适用于某个特定的类。比如summary(),它是生成摘要的函数族,R会为要处理的类寻找合适的摘要函数,并使用比列表更加合理的方式来展示。因此对于hist()和lm()它会调用不同的摘要函数。(lm是linear model的缩写)。同样的plot()也是这样的。
代码语言:javascript复制> a <- hist(Nile)
> summary(a)
Length Class Mode
breaks 11 -none- numeric
counts 10 -none- numeric
density 10 -none- numeric
mids 10 -none- numeric
xname 1 -none- character
equidist 1 -none- logical
> plot(a)
> b <- lm(Nile~c(1:100))
> plot(b)
Hit <Return> to see next plot:
Hit <Return> to see next plot:
Hit <Return> to see next plot:
Hit <Return> to see next plot:
> summary(b)
Call:
lm(formula = Nile ~ c(1:100))
Residuals:
Min 1Q Median 3Q
-483.71 -98.17 -23.21 111.40
Max
368.72
Coefficients:
Estimate Std. Error
(Intercept) 1056.4224 30.3377
c(1:100) -2.7143 0.5216
t value Pr(>|t|)
(Intercept) 34.822 < 2e-16 ***
c(1:100) -5.204 1.07e-06 ***
---
Signif. codes:
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05
‘.’ 0.1 ‘ ’ 1
Residual standard error: 150.6 on 98 degrees of freedom
Multiple R-squared: 0.2165, Adjusted R-squared: 0.2085
F-statistic: 27.08 on 1 and 98 DF, p-value: 1.072e-06
S3对象
S3对象系统是一个简单且宽松的面向对象系统。每个基本对象的类型都有一个S3类名称。比如integer,numeric, character, logical, list和data.frame都属于S3类。
S3类内部是一个list,append某个list类名称,就能成为该类。list里面的内容就是我们所说的属性.
首先创建一个list
代码语言:javascript复制> dnaseq = list(seq = "ATGC", length = nchar("ATGC"))
> dnaseq
$seq
[1] "ATGC"
$length
[1] 4
现在dnaseq这个list只属于list类.
然后我们append 一个类名"DNAseq",就这样我们创建了一个DNAseq类,类的属性有seq和length,值为ATGC和4。
代码语言:javascript复制> class(dnaseq) = append(class(dnaseq),"DNAseq")
> class(dnaseq)
[1] "list" "DNAseq"
我们可以通过普通的list的方法来获得类的属性,比如:
代码语言:javascript复制> dnaseq$seq
[1] "ATGC"
> dnaseq$length
[1] 4
S3类的创建
简单直接的构建方法
依据刚才的类的结构,我们用函数进行类的构建,函数的输入是要传入进行类的初始化的值,而函数的返回就是新生成的类。这样我们就可以根据不同的初始化值进行类的实例化。
首先构造一个类。
代码语言:javascript复制# Straight forward approach
DNAseq <- function(seq = "ATGCATGCATGCATGCATGC"){
me <- list(
seq = seq,
length = nchar(seq)
)
# Set the name for the class
class(me) <- append(class(me), "DNAseq")
return(me)
}
类的实例
代码语言:javascript复制> seq1 <- DNAseq()
> seq1
$seq
[1] "ATGCATGCATGCATGCATGC"
$length
[1] 20
attr(,"class")
[1] "list" "DNAseq"
> seq2 <- DNAseq(seq="TGCATGCATG")
> seq2
$seq
[1] "TGCATGCATG"
$length
[1] 10
attr(,"class")
[1] "list" "DNAseq"
创建方法
类中除了含有属性外,肯定还得含有方法。在创建方法之前我们首先得用这个方法的名字创建一个函数,这样运行函数时首先进入这个函数,然后在函数里面使用useMethod函数,在环境中寻找该类的该方法。虽然下面的代码比较复杂,但是重点时看UseMethod。
代码语言:javascript复制# Creating methods
reverseComplement <- function(object){
UseMethod("reverseComplement", object)
}
reverseComplement.default <- function(object){
print("The class of this object can not be found")
}
# Straight forward approach
#
# For S3 classes created by Straight forward approach
reverseComplement.DNAseq <- function(object){
print("Calling the reverseComplement function of DNAseq class")
## Compelement according to the vector below
to_base <- c("A", "T", "G", "C")
names(to_base) <- c("T", "A", "C", "G")
## Transform long charactor to vector and complement
trans_seq_vect <- to_base[unlist(strsplit(object$seq, split = ""))]
## Reverse
trans_rev_vect <- trans_seq_vect[length(trans_seq_vect):1]
## Collape to long character
newseq <- paste0(trans_rev_vect, collapse = "")
# Return a new DNAseq class
return(DNAseq(newseq))
}
# For S3 classed created by local enviroment approach
reverseComplement.DNASeq <- function(object){
print("Calling the reverseComplement function of DNASeq class")
## Compelement according to the vector below
to_base <- c("A", "T", "G", "C")
names(to_base) <- c("T", "A", "C", "G")
## Transform long charactor to vector and complement
trans_seq_vect <- to_base[unlist(strsplit(get("seq", seq2$getEnv()), split = ""))]
## Reverse
trans_rev_vect <- trans_seq_vect[length(trans_seq_vect):1]
## Collape to long character
newseq <- paste0(trans_rev_vect, collapse = "")
# Return a new DNASeq class
return(DNASeq(newseq))
上面还有一个default函数,表示默认的方法,如果该类找不到该类匹配的方法,就会使用默认方法。
类继承
S3类可以使用继承,在原来类的基础上再append一个新的类名即为新的类,用NextMethod可以调用下一层类的方法。
创建一个primer类继承DNAseq类
代码语言:javascript复制# Straight forward approach
DNAseq <- function(seq = "ATGCATGCATGCATGCATGC"){
me <- list(
seq = seq,
length = nchar(seq)
)
# Set the name for the class
class(me) <- append(class(me), "DNAseq")
return(me)
}
#inheritance
Primer <- function(seq = "ATGCATGCATGCATGCATGCGGCC"){
pr <- strtrim(seq, 20)
me <- DNAseq(pr)
class(me) <- append(class(me), "Primer")
return(me)
}
Primer1 <- Primer()
$seq
[1] "ATGCATGCATGCATGCATGC"
$length
[1] 20
attr(,"class")
[1] "list" "DNAseq" "Primer"
调用方法的时候会按照从左到右的顺序,再这个例子中,默认先调用DNAseq的方法,如果想要调用Primer类的方法,首先写一个Primer的reverseComplement方法。
代码语言:javascript复制# Creating methods
reverseComplement.Primer <- function(object){
print("Running reverseComplement of Primer class")
}
然后在DNAseq类中调用下一类的方法,使用NextMethod。
代码语言:javascript复制reverseComplement.DNAseq <- function(object){
print("Calling the reverseComplement function of DNAseq class")
NextMethod("reverseComplement", object)
## Compelement according to the vector below
to_base <- c("A", "T", "G", "C")
names(to_base) <- c("T", "A", "C", "G")
## Transform long charactor to vector and complement
trans_seq_vect <- to_base[unlist(strsplit(object$seq, split = ""))]
## Reverse
trans_rev_vect <- trans_seq_vect[length(trans_seq_vect):1]
## Collape to long character
newseq <- paste0(trans_rev_vect, collapse = "")
# Return a new DNAseq class
return(DNAseq(newseq))
}
代码语言:javascript复制reverseComplement(Primer1)
[1] "Calling the reverseComplement function of DNAseq class"
[1] "Running reverseComplement of Primer class"
$seq
[1] "GCATGCATGCATGCATGCAT"
$length
[1] 20
attr(,"class")
[1] "list" "DNAseq"
代码语言:javascript复制reverseComplement(seq1)
[1] "Calling the reverseComplement function of DNAseq class"
[1] "The class of this object can not be found"
$seq
[1] "GCATGCATGCATGCATGCAT"
$length
[1] 20
attr(,"class")
[1] "list" "DNAseq"
寻找泛型函数的实现方法
可以调用methods()来找到泛型函数的所有实现方法,比如:
代码语言:javascript复制> methods(print)
[1] print.acf*
[2] print.anova*
[3] print.aov*
[4] print.aovlist*
[5] print.ar*
[6] print.Arima*
[7] print.arima0*
[8] print.AsIs
[9] print.aspell*
[10] print.aspell_inspect_context*
[11] print.bibentry*