面向对象
A class defines the behaviour of objects by describing their attributes and their relationship to other classes.
The class is also used when selecting methods, functions that behave differently depending on the class of their input.
- 基于消息传递的OO系统:With message-passing, messages (methods) are sent to objects and the object determines which function to call.
- 基于泛型函数的OO系统:a special type of function called a generic function decides which method to call.
library(pryr)
Base types
- functions
- environments
- names
- calls
- promises
f <- function() {}
typeof(f)
## [1] "closure"
is.function(f)
## [1] TRUE
typeof(sum)
## [1] "builtin"
is.primitive(sum)
## [1] TRUE
S3对象
S对象统是最普遍使用的对象系统。是非正式的并且简洁的。
识别对象,泛型函数和方法
大多数对象都是S3对象。
library(pryr)
df <- data.frame(x = 1:10, y = letters[1:10])
otype(df)
## [1] "S3"
df是S3对象
otype(df$x)
## [1] "base"
numeric vector不是S3
otype(df$y)
## [1] "S3"
在S3中,方法属于函数,称为泛型函数,或泛型。
S3 methods do not belong to objects or classes.
查看一个函数是否是泛型,可以看是否有UseMethod,或ftype()
mean
## function (x, ...)
## UseMethod("mean")
## <bytecode: 0x000000000af17700>
## <environment: namespace:base>
ftype(mean)
## [1] "s3" "generic"
library(Mreport)
## 载入需要的程辑包:leafletCN
ftype(geo_pointplot)
## [1] "function"
ftype(caculate_carsmean)
## [1] "function"
ftype(sum)
## [1] "primitive" "generic"
ftype(`[`)
## [1] "primitive" "generic"
Given a class, the job of an S3 generic is to call the right S3 method. You can recognise S3 methods by their names, which look like generic.class(). For example, the Date method for the mean() generic is called mean.Date(), and the factor method for print() is called print.factor().
ftype(t.data.frame)
## [1] "s3" "method"
ftype(t.test)
## [1] "s3" "generic"
You can see all the methods that belong to a generic with methods()
methods("mean")
## [1] mean.Date mean.default mean.difftime mean.POSIXct mean.POSIXlt
## see '?methods' for accessing help and source code
methods("t.test")
## [1] t.test.default* t.test.formula*
## see '?methods' for accessing help and source code
You can also list all generics that have a method for a given class:
methods(class = "ts")
## [1] [ [<- aggregate as.data.frame cbind
## [6] coerce cycle diff diffinv initialize
## [11] kernapply lines Math Math2 monthplot
## [16] na.omit Ops plot print show
## [21] slotsFromS3 t time window window<-
## see '?methods' for accessing help and source code
定义类和创建对象
To make an object an instance of a class, you just take an existing base object and set the class attribute. You can do that during creation with structure(), or after the fact with class<-():
foo <- list()
class(foo) <- "foo"
class(foo)
## [1] "foo"
S3 objects are usually built on top of lists, or atomic vectors with attributes.
class(foo)
## [1] "foo"
inherits(foo, "foo")
## [1] TRUE
Most S3 classes provide a constructor function:
foo <- function(x) {
if (!is.numeric(x)) stop("X must be numeric")
structure(list(x), class = "foo")
}
a <- 1:3
a <- foo(a)
class(a)
## [1] "foo"
you can change the class of existing objects:
mod <- lm(log(mpg) ~ log(disp), data = mtcars)
class(mod)
## [1] "lm"
print(mod)
##
## Call:
## lm(formula = log(mpg) ~ log(disp), data = mtcars)
##
## Coefficients:
## (Intercept) log(disp)
## 5.3810 -0.4586
class(mod) <- "data.frame"
print(mod)
## [1] coefficients residuals effects rank fitted.values
## [6] assign qr df.residual xlevels call
## [11] terms model
## <0 行> (或0-长度的row.names)
mod$coefficients
## (Intercept) log(disp)
## 5.3809725 -0.4585683
创建方法和泛型
To add a new generic, create a function that calls UseMethod(). UseMethod() takes two arguments: the name of the generic function, and the argument to use for method dispatch.
There’s no need to pass any of the arguments of the generic to UseMethod() and you shouldn’t do so.
f <- function(x) UseMethod("f")
ftype(f)
## [1] "s3" "generic"
A generic isn’t useful without some methods. To add a method, you just create a regular function with the correct (generic.class) name:
f.a <- function(x) "Class a"
a <- structure(list(), class = "a")
class(a)
## [1] "a"
f(a)
## [1] "Class a"
Adding a method to an existing generic works in the same way:
mean.a <- function(x) "hello a"
mean(a)
## [1] "hello a"
方法分配
f <- function(x) UseMethod("f")
f.a <- function(x) "Class a"
f.default <- function(x) "Unknown class"
f(structure(list(), class = "a"))
## [1] "Class a"
f(structure(list(), class = c("b", "a")))
## [1] "Class a"
f(structure(list(), class = "c"))
## [1] "Unknown class"
Because methods are normal R functions, you can call them directly:
c <- structure(list(), class = "c")
f(c)
## [1] "Unknown class"
f.default(c)
## [1] "Unknown class"
f.a(c)
## [1] "Class a"
However, this is just as dangerous as changing the class of an object, so you shouldn’t do it.
练习
练习1
ftype(t.test)
## [1] "s3" "generic"
body(t.test)
## UseMethod("t.test")
methods(t.test)
## [1] t.test.default* t.test.formula*
## see '?methods' for accessing help and source code
a <- structure(list(),class="test")
class(a)
## [1] "test"
try(t(a))
## Warning in mean.default(x): 参数不是数值也不是逻辑值:回覆NA
调用了t.default
body(t.default)
## .Internal(t.default(x))
练习3
methods(class = "POSIXt")
## [1] - + all.equal as.character Axis
## [6] coerce cut diff hist initialize
## [11] is.numeric julian Math months Ops
## [16] pretty quantile quarters round seq
## [21] show slotsFromS3 str trunc weekdays
## see '?methods' for accessing help and source code
methods(class = "POSIXct")
## [1] [ [[ [<- as.data.frame as.Date
## [6] as.list as.POSIXlt c coerce format
## [11] initialize mean print rep show
## [16] slotsFromS3 split summary Summary weighted.mean
## [21] xtfrm
## see '?methods' for accessing help and source code
methods(class = "POSIXlt")
## [1] [ [<- anyNA as.data.frame as.Date
## [6] as.double as.matrix as.POSIXct c coerce
## [11] duplicated format initialize is.na length
## [16] mean names names<- print rep
## [21] show slotsFromS3 sort summary Summary
## [26] unique weighted.mean xtfrm
## see '?methods' for accessing help and source code
all3 <- list(methods(class = "POSIXct"),methods(class = "POSIXlt"),methods(class = "POSIXt"))
all3 <- lapply(all3, function(x) attr(x,"info")$generic)
(allgen <- Reduce(union,all3))
## [1] "[" "[[" "[<-" "as.data.frame"
## [5] "as.Date" "as.list" "as.POSIXlt" "c"
## [9] "coerce" "format" "initialize" "mean"
## [13] "print" "rep" "show" "slotsFromS3"
## [17] "split" "summary" "Summary" "weighted.mean"
## [21] "xtfrm" "anyNA" "as.double" "as.matrix"
## [25] "as.POSIXct" "duplicated" "is.na" "length"
## [29] "names" "names<-" "sort" "unique"
## [33] "-" "+" "all.equal" "as.character"
## [37] "Axis" "cut" "diff" "hist"
## [41] "is.numeric" "julian" "Math" "months"
## [45] "Ops" "pretty" "quantile" "quarters"
## [49] "round" "seq" "str" "trunc"
## [53] "weekdays"
练习4
那个基础泛型函数拥有最多的方法?
objs <- mget(ls("package:base"),inherits=T)
funs <- Filter(is.function,objs)
funsl2 <- Filter(function(x) "generic" %in% ftype(x),funs)
length(funsl2)
## [1] 176
len <- vapply(names(funsl2), compose(length,methods),FUN.VALUE = numeric(1))
names(funsl2)[len==max(len)]
## [1] "print"
funsl2[len==max(len)]
## $print
## function (x, ...)
## UseMethod("print")
## <bytecode: 0x0000000008770bb8>
## <environment: namespace:base>