前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >R语言基础教程——第7章:面向对象编程(S3类)

R语言基础教程——第7章:面向对象编程(S3类)

作者头像
DoubleHelix
发布2019-08-06 15:21:16
3.2K0
发布2019-08-06 15:21:16
举报
文章被收录于专栏:生物信息云

面向对象是一种对现实世界理解和抽象的方法,当代码复杂度增加难以维护的时候,面向对象就会显得非常重要。学过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* 
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-07-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 MedBioInfoCloud 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是面向对象?
  • R为什么要进行面向对象编程?
  • R的面向对象编程
  • S3对象
  • S3类的创建
  • 创建方法
  • 类继承
  • 寻找泛型函数的实现方法
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档