专栏首页生物信息云R语言基础教程——第7章:面向对象编程(S3类)

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

面向对象是一种对现实世界理解和抽象的方法,当代码复杂度增加难以维护的时候,面向对象就会显得非常重要。学过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()也是这样的。

> 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

> dnaseq = list(seq = "ATGC", length = nchar("ATGC"))
> dnaseq
$seq
[1] "ATGC"
$length
[1] 4

现在dnaseq这个list只属于list类.

然后我们append 一个类名"DNAseq",就这样我们创建了一个DNAseq类,类的属性有seq和length,值为ATGC和4。

> class(dnaseq) = append(class(dnaseq),"DNAseq")
> class(dnaseq)
[1] "list"       "DNAseq" 

我们可以通过普通的list的方法来获得类的属性,比如:

> dnaseq$seq
[1] "ATGC"
> dnaseq$length
[1] 4

S3类的创建

简单直接的构建方法

依据刚才的类的结构,我们用函数进行类的构建,函数的输入是要传入进行类的初始化的值,而函数的返回就是新生成的类。这样我们就可以根据不同的初始化值进行类的实例化。

首先构造一个类。

# 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)
}

类的实例

> 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。

# 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类

# 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方法。

# Creating methods 
reverseComplement.Primer <- function(object){
  print("Running reverseComplement of Primer class")
}

然后在DNAseq类中调用下一类的方法,使用NextMethod。

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))
}
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"
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()来找到泛型函数的所有实现方法,比如:

> 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* 

本文分享自微信公众号 - MedBioInfoCloud(MedBioInfoCloud),作者:DoubleHelix

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-28

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 利用WGCNA识别肺鳞状细胞癌关键基因巧妙发4分+

    今天解读的这篇文章是2020年发表在International Journal of Molecular Sciences杂志上(影响因子4.183)。题目是I...

    DoubleHelix
  • RNA-seq的counts,RPM, RPKM, FPK值到底有什么区别?

    现在常用的基因定量方法包括:RPM, RPKM, FPKM, TPM。这些表达量的主要区别是:通过不同的标准化方法为转录本丰度提供一个数值表示,以便于后续差异分...

    DoubleHelix
  • R语言基础教程——第3章:RStudio的使用教程

    在前面我们介绍了R和RStudio的安装教程,也简单介绍R的GUI的使用,包括包的安装,加载等进行简单的介绍,然而并不详细,对于初学者来说,可能很难理解,原因...

    DoubleHelix
  • SpringBoot启动报错Failed to determine a suitable driver class

    应用没有使用到DataSource,但是在pom.xml里引入了mybatis-spring-boot-starter

    道可道非常道
  • Kubernetes的DaemonSet(上篇)

    静儿作为美团容器化团队HULK的一员,经常需要和Kubernetes(k8s)打交道。第一次登陆node(宿主机)的时候,发现连续登陆几台都看到了Prometh...

    静儿
  • python日志级别

    py3study
  • 【Oracle】-【创建索引】-创建索引的操作原理与一些体会

    1、将index key的data读到cache。如果之前这部分数据未读到DB Cache,那么此时可能有db file scatter read write的...

    bisal
  • Python和Scala的序列

    序列是一门高级语言里都会具备的一种数据结构,Scala和Python也不例外。在不同的语言里,序列有着各种不同的别称以及增添了不同的功能,今天只关注Scala和...

    哒呵呵
  • Java 二进制文件操作大全

    中国广东省深圳市望海路半岛城邦三期 518067 +86 13113668890 <netkiller@msn.com>

    netkiller old
  • 《Netkiller Java 手札》之 二进制文件操作大全

    中国广东省深圳市望海路半岛城邦三期 518067 +86 13113668890 <netkiller@msn.com>

    netkiller

扫码关注云+社区

领取腾讯云代金券