S4对象(深化)

很久之前就做过相应练习,但是最近觉得还是将正文翻译一下比较好

面向对象编程(OOP) 和自定义类

  • https://adv-r.hadley.nz/oo.html

  • R 支持多个不同的面向对象编程(Object-oriented programming) 系统,实现对数据类型有特定需求的包

  • 常见的有: S3, S4, RC, R6, R.oo, proto

  • 很多包有自定义的S3, S4, R6 类

  • 这种包的大多数函数属于类方法,所以只对特定类型的数据起作用

  • 基础R 广泛依赖于S3 类

查看对象的类型

#install.packages("sloop")  
sloop::otype(mtcars)  
#sloop::otype()

Welcome | Advanced R (hadley.nz)

其实R的对象更像是一种数据结构,首先定义一个对象的名字,slot在英文中是投入,放入的意思
现在我设置这个对象有以下的几种属性或者说特征,然后我要规定这些属性和特征是怎么样的数据类型
并且这些数据类型可以自定义

setClass(Class = "fakeSe",slots =c(x="numeric",data="data.frame",comment="character",wtf="character"))  
x=c(1:10)  
x  
tt<-new("fakeSe",x=x,data=data.frame(x=x),comment="my data")  
tt  
class(tt)  
sloop::otype(tt)  
#/然后可以写一个专门的函数来处理自己的对象  
doublex<-function(object){  
 object@x<-object@x*2  
 object@data<-data.frame(x=object@x)  
 return( object)  
}  
  

image
tt2<-doublex(tt)  
tt2

练习S4对象

15 S4 | Advanced R (hadley.nz)

应该把S4对象看作一个excel表格,里面有好几张表,现在你要设定一个模板,你要规定每张表的类型是什么,是只能写数字还是只能写字符串等等,然后你现在就可以根据这个写好的模板进行创建新的对象了。这些使用setclass就可以做到了

但是你还不满足

你要设计一套能够专门针对这个模板的操作,也就是针对这个函数的一系列的方法

但是你觉得专门针对这个对象搞一个函数太麻烦了,于是就可以利用以前R里面本身就有的一些函数进行分析,比如union()函数,这样就可以省一点功夫,相当于给以前的函数重新定义了一下

但是你也可以从头创建一个泛型函数

S4 提供了一种正式的函数式 OOP 方法。
底层思想类似于 S3(https://adv-r.hadley.nz/s3.html#s3),但实现要严格得多,并且使用专门的函数来创建类(setClass()),泛型(setGeneric()) 和方法(setMethod())。此外,S4 提供多重继承(即一个类可以有多个父类)和多重分派(即方法分派可以使用多个参数的类)。

S4 的一个重要的新组件是 slot,它是使用专门的子集运算符 @ 访问的对象的命名组件(发音为at)。slots集及其类构成了 S4 类定义的重要部分。

setClass("Person",   
 slots = c(  
 name = "character",   
 age = "numeric"  
 )  
)  
john <- new("Person", name = "John Smith", age = NA_real_)  
john  


is(john)  
#> [1] "Person"  
john@name  
#> [1] "John Smith"  
slot(john, "age")  
#> [1] NA  


setGeneric("age", function(x) standardGeneric("age"))  
setGeneric("age<-", function(x, value) standardGeneric("age<-"))  
setMethod("age", "Person", function(x) x@age)  
setMethod("age<-", "Person", function(x, value) {  
 x@age <- value  
 x  
})  
  
age(john) <- 50  
age(john)  
#> [1] 50

15.3 Classes

要定义 S4 类,请使用三个参数调用 setClass()

  • 名称。按照惯例,S4 类名使用 UpperCamelCase

  • 描述 slots(字段)的名称和类的命名字符向量。例如,一个人可能由一个字符名称和一个数字年龄表示:[c(name = "character", age = "numeric")](https://rdrr.io/r/base/c.html)。伪类“ANY”允许插槽接受任何类型的对象。

  • prototype,每个slots的默认值列表。从技术上讲,prototype是可选的,但您应该始终提供它

下面的代码通过创建一个带有字符“name”和数字“age”槽的“Person”类来说明三个参数。

setClass("Person",   
 slots = c(  
 name = "character",   
 age = "numeric"  
 ),   
 prototype = list(  
 name = NA_character_,  
 age = NA_real_  
 )  
)  
  
me <- new("Person", name = "Hadley")  
str(me)  
#> Formal class 'Person' [package ".GlobalEnv"] with 2 slots  
#>   ..@ name: chr "Hadley"  
#>   ..@ age : num NA

15.3.1 继承

继承

也就是能够继承前面的其他的对象的内容

setClass() 的另一个重要参数是:contains。这指定了一个(或多个)类来继承slots和behaviour。例如,我们可以创建一个从 Person 类继承的 Employee 类,添加一个额外的slots来描述他们的 boss

setClass("Employee",   
 contains = "Person",   
 slots = c(  
 boss = "Person"  
 ),  
 prototype = list(  
 boss = new("Person")  
 )  
)  
  
str(new("Employee"))  
#> Formal class 'Employee' [package ".GlobalEnv"] with 3 slots  
#>   ..@ boss:Formal class 'Person' [package ".GlobalEnv"] with 2 slots  
#>   .. .. ..@ name: chr NA  
#>   .. .. ..@ age : num NA  
#>   ..@ name: chr NA  
#>   ..@ age : num NA  

15.3.2 Introspection

判断对象是对谁进行继承

要确定对象继承自哪些类,使用 is():

is(new("Person"))  
#> [1] "Person"  
is(new("Employee"))  
#> [1] "Employee" "Person"

To test if an object inherits from a specific class, use the second argument of [`is()`](https://rdrr.io/r/methods/is.html):

is(john, "Person")  
#> [1] TRUE  

15.3.3 Redefinition

重定义对象之后,之前的对象就会出错

在大多数编程语言中,类定义发生在编译时,对象构造发生在运行时。然而,在 R 中,定义和构造都发生在运行时。当您调用 setClass() 时,您正在(隐藏的)全局变量中注册类定义。与所有状态修改函数一样,您需要小心使用 setClass()。如果在实例化对象后重新定义类,则可能会创建无效对象:

setClass("A", slots = c(x = "numeric"))  
a <- new("A", x = 10)  
  
setClass("A", slots = c(a_different_slot = "numeric"))  
  
a <- new("A", a_different_slot  = 10)  
a  
  
#> An object of class "A"  
#> Slot "a_different_slot":  
#> Error in slot(object, what): no slot of name "a_different_slot" for this object  
#> of class "A"  

15.4 Generics and methods

泛型的工作是执行方法分派,即找到传递给泛型的类组合的具体实现。在这里,您将学习如何定义 S4 泛型和方法,然后在下一节中,我们将准确探索 S4 方法分派的工作原理。

要创建一个新的 S4 泛型,调用 setGeneric() 和一个调用 [standardGeneric()](https://rdrr.io/r/base/standardGeneric.html):

setGeneric("myGeneric", function(x) standardGeneric("myGeneric"))

按照惯例,新的 S4 泛型应该使用 lowerCamelCase

在泛型中使用 {} 是不好的做法,因为它会触发代价更高的特殊情况,通常最好避免。

Don't do this!

setGeneric("myGeneric", function(x) {  
 standardGeneric("myGeneric")  
})  

15.4.1 Signature

也就是特点,可以使用...进行省略
setClass(), setGeneric()有许多其他论据。只有一个你需要知道:signature。这允许您控制用于方法分派的参数。如果未提供signature,则使用所有参数(除了...)。从调度中删除参数有时很有用。这允许您要求方法提供诸如“verbose = TRUE”或“quiet = FALSE”之类的参数,但它们不参与调度。

setGeneric("myGeneric",   
 function(x, ..., verbose = TRUE) standardGeneric("myGeneric"),  
 signature = "x"  
)  

15.4.2 Methods

没有一些methods,泛型(generic)就没有用处,在 S4 中,您可以使用 setMethod() 定义方法。有三个重要的参数:泛型的名称、类的名称和方法本身。

setMethod("myGeneric", "Person", function(x) {  
 # method implementation  
})  
  

更正式地说,setMethod() 的第二个参数称为signature。在 S4 中,与 S3 不同,signature可以包含多个参数。这使得 S4 中的方法分派变得更加复杂,但避免了必须将双分派作为特殊情况来实现。我们将在下一节中详细讨论多重分派。 setMethod() 有其他参数,但你不应该使用它们。

要列出属于泛型或与类关联的所有方法,请使用 methods("generic") 或 [方法(类=“类”)](https://rdrr.io/r/utils/methods.html);要查找特定方法的实现,请使用 selectMethod("generic", "class")

args(getGeneric("show"))  
#> function (object)   
#> NULL

setMethod("show", "Person", function(object) {  
 cat(is(object)[[1]], "\n",  
 "  Name: ", object@name, "\n",  
 "  Age:  ", object@age, "\n",  
 sep = ""  
 )  
})  
john  
#> Person  
#>   Name: John Smith  
#>   Age:  50

15.4.3 Show method

最常定义的控制打印的 S4 方法是 show(),它控制对象在打印时的显示方式。要为现有泛型定义方法,您必须首先确定参数。您可以从文档中或通过查看泛型的 args() 获取这些

args(getGeneric("show"))  
#> function (object)   
#> NULL

setMethod("show", "Person", function(object) {  
 cat(is(object)[[1]], "\n",  
 "  Name: ", object@name, "\n",  
 "  Age:  ", object@age, "\n",  
 sep = ""  
 )  
})  
john  
#> Person  
#>   Name: John Smith  
#>   Age:  50  


15.4.4 Accessors

应该考虑Slots内部的实现细节:它们可以在没有警告的情况下更改,并且用户代码应避免直接访问它们。相反,所有用户可访问的slots都应该带有一对accessors。如果slots对类来说是唯一的,则这可以只是一个函数

person_name <- function(x) x@name  


setGeneric("name", function(x) standardGeneric("name"))  
setMethod("name", "Person", function(x) x@name)  
  
name(john)  
#> [1] "John Smith"  


setGeneric("name<-", function(x, value) standardGeneric("name<-"))  
setMethod("name<-", "Person", function(x, value) {  
 x@name <- value  
 validObject(x)  
 x  
})  
  
name(john) <- "Jon Smythe"  
name(john)  
#> [1] "Jon Smythe"  
  
name(john) <- letters  
#> Error in validObject(x): invalid class "Person" object: @name and @age must be  
#> same length
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容