很久之前就做过相应练习,但是最近觉得还是将正文翻译一下比较好
面向对象编程(OOP) 和自定义类
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)
}

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