前言
1. 面向?qū)ο?/h3>
面向?qū)ο缶幊蹋?code>object-oriented programming,OOP
)是一種編程范式,它將對(duì)象作為程序的基本單元查辩,一個(gè)對(duì)象包含了數(shù)據(jù)以及操作數(shù)據(jù)的函數(shù)骏全。
那什么是對(duì)象?對(duì)象是類(class
)類的實(shí)例肪康。
那什么又是類呢荚恶?
類是對(duì)現(xiàn)實(shí)事物的抽象撩穿,比如說(shuō),人類是對(duì)世界上所有人的總稱谒撼,而你食寡、我卻是實(shí)實(shí)在在存在于現(xiàn)實(shí)中的,也就是一個(gè)個(gè)對(duì)象廓潜。
類的定義包含了對(duì)數(shù)據(jù)的描述以及對(duì)應(yīng)的操作方法抵皱,比如,人應(yīng)該有性別辩蛋、年齡呻畸、身高、體重等固有特征悼院,但是每個(gè)對(duì)象伤为,也就是說(shuō)雖然每個(gè)人的特征千差萬(wàn)別,但都有這些固定的屬性客觀存在的据途。
2. R 的面向?qū)ο缶幊?/h3>
之前绞愚,我們對(duì) R
的理解可能都是停留在函數(shù)式編程的概念里。也就是編寫一個(gè)個(gè)函數(shù)颖医,來(lái)處理不同的對(duì)象爽醋。
當(dāng)然,目前 R
主要用于統(tǒng)計(jì)計(jì)算便脊,而且代碼量一般不會(huì)很大蚂四,幾十或上百行。使用函數(shù)式的編程方式就可以很好的完成編程任務(wù)哪痰。
一般來(lái)說(shuō)遂赠,在 R
中,函數(shù)式編程要比面向?qū)ο缶幊讨匾枚嗌谓埽驗(yàn)槟阃ǔJ菍?fù)雜的問(wèn)題分解成簡(jiǎn)單的函數(shù)跷睦,而不是簡(jiǎn)單的對(duì)象。
那為什么我還要學(xué)習(xí)面向?qū)ο缶幊棠兀?/p>
面向?qū)ο缶幊痰膬?yōu)勢(shì)是肋演,能夠使程序便于分析抑诸、設(shè)計(jì)、理解爹殊,提高重用性蜕乡、靈活性和可擴(kuò)展性。
R
中的 OOP
系統(tǒng)
base R
提供的:S3
,S4
和reference classes
(RC
)CRAN
包提供的:R6
梗夸、R.oo
层玲、proto
S3
1.1 概念
S3
面向?qū)ο缶幊蹋?R
中第一個(gè)也是最簡(jiǎn)單的 OOP
系統(tǒng),廣泛存在于早期開發(fā)的 R
包中辛块,也是 CRAN
包最常用的系統(tǒng)畔派。
S3
的實(shí)現(xiàn)是基于一種特殊的函數(shù)(泛型函數(shù),根據(jù)傳入對(duì)象的類型來(lái)決定調(diào)用哪個(gè)方法)
1.2 創(chuàng)建 S3 對(duì)象
注意:下面我們會(huì)使用 sloop
包提供的函數(shù)來(lái)幫助我們查看對(duì)象的類型
> install.packages('sloop')
> library(sloop)
首先润绵,我們使用 attr
來(lái)創(chuàng)建一個(gè) S3
對(duì)象
> a <- 1
> attr(a, 'class') <- 'bar'
> a
[1] 1
attr(,"class")
[1] "bar"
使用 class
或 attr
獲取對(duì)象的類型
> class(a)
[1] "bar"
> attr(a, 'class')
[1] "bar"
再用 sloop
包的 otype
來(lái)判斷是何種對(duì)象
> otype(a)
[1] "S3"
> otype(1)
[1] "base"
我們也可以使用 structure
來(lái)構(gòu)建一個(gè) S3
對(duì)象
> b <- structure(2, class='foo')
> b
[1] 2
attr(,"class")
[1] "foo"
> otype(b)
[1] "S3"
還可以使用為 class(var)
賦值的方式構(gòu)建
> x <- list(a=1)
> class(x)
[1] "list"
> otype(x)
[1] "base"
> class(x) <- 'foo'
> class(x)
[1] "foo"
> otype(x)
[1] "S3"
還可以將類屬性設(shè)置為向量线椰,為 S3
對(duì)象指定多個(gè)類型
> c <- structure(3, class=c('bar', 'foo'))
> class(c)
[1] "bar" "foo"
> otype(c)
[1] "S3"
1.3 創(chuàng)建泛型函數(shù)
通常,我們使用 UseMethod()
來(lái)創(chuàng)建一個(gè)泛型函數(shù)尘盼,例如
person <- function(x, ...) {
UseMethod('person')
}
定義完泛型函數(shù)之后士嚎,可以使用以下方式
-
person.xxx
定義名為xxx
的方法 -
person.default
定義默認(rèn)方法
person.default <- function(x, ...) {
print("I am human.")
}
person.sing <- function(x, ...) {
print("I can sing")
}
person.name <- function(x, ...) {
print(paste0("My name is ", x))
}
那如何調(diào)用這些方法呢?
首先悔叽,我們定義一個(gè) class
屬性為 "sing"
的變量
> a <- structure("tom", class='sing')
然后,將該對(duì)象 a
傳入 person
中
> person(tom)
[1] "I can sing"
> person.sing(a)
[1] "I can sing"
可以看到爵嗅,調(diào)用了 person.sing()
方法娇澎。
讓我們?cè)賴L試其他類型
> b <- structure("tom", class='name')
> person(b)
[1] "My name is tom"
> person("joy")
[1] "I am human."
這樣,我們只要使用 person
函數(shù)睹晒,就能夠?qū)Σ煌愋偷妮斎胱龀鱿鄳?yīng)趟庄,輸入不同類型的對(duì)象會(huì)自動(dòng)調(diào)用相應(yīng)的方法。
對(duì)于未指定的類型伪很,會(huì)調(diào)用 person.default
方法戚啥。這就是泛型函數(shù)。
1.4 S3 對(duì)象的方法
我們可以使用 methods()
函數(shù)來(lái)獲取 S3
對(duì)象包含的所有方法
> methods(person)
[1] person.default person.name person.sing
可以使用 generic.function
參數(shù)锉试,傳遞想要查詢的泛型函數(shù)
> library(magrittr)
> methods(generic.function = print) %>% head()
[1] "print.acf" "print.anova" "print.aov" "print.aovlist"
[5] "print.ar" "print.Arima"
class
參數(shù)指定類名
> methods(class = lm) %>% head()
[1] "add1.lm" "alias.lm"
[3] "anova.lm" "case.names.lm"
[5] "coerce,oldClass,S3-method" "confint.lm"
注意:一些輸出的函數(shù)名后綴有 *
號(hào)表示不可見(jiàn)函數(shù)猫十,例如
> print.xtabs
錯(cuò)誤: 找不到對(duì)象'print.xtabs'
可以使用 getAnywhere
獲取
> getAnywhere(print.xtabs)
A single object matching ‘print.xtabs’ was found
It was found in the following places
registered S3 method for print from namespace stats
namespace:stats
with value
function (x, na.print = "", ...)
{
ox <- x
attr(x, "call") <- NULL
print.table(x, na.print = na.print, ...)
invisible(ox)
}
<bytecode: 0x7fe1e612b9e8>
<environment: namespace:stats>
或者 getS3method
> getS3method("print", "xtabs")
function (x, na.print = "", ...)
{
ox <- x
attr(x, "call") <- NULL
print.table(x, na.print = na.print, ...)
invisible(ox)
}
<bytecode: 0x7fe1e612b9e8>
<environment: namespace:stats>
1.5 S3 對(duì)象的繼承
S3
對(duì)象是通過(guò) NextMethod()
方法繼承的,讓我們先定義一個(gè)泛型函數(shù)
person <- function(x, ...) {
UseMethod('person')
}
person.father <- function(x, ...) {
print("I am father.")
}
person.son <- function(x, ...) {
NextMethod()
print("I am son.")
}
執(zhí)行
> p1 <- structure(1,class=c("father"))
> person(p1)
[1] "I am father."
> p2 <- structure(1,class=c("son","father"))
> person(p2)
[1] "I am father."
[1] "I am son."
可以看到呆盖,在調(diào)用 person(p2)
之后拖云,會(huì)先執(zhí)行 person.father()
然后執(zhí)行 person.son()
注意:需要將被繼承的類型放在第二個(gè)(son
之后)
> ab <- structure(1, class = c("father", "son"))
> person(ab)
[1] "I am father."
這樣就實(shí)現(xiàn)了面向?qū)ο缶幊讨械睦^承
1.6 缺點(diǎn)
-
S3
并不是完全的面向?qū)ο螅腔诜盒秃瘮?shù)模擬的面向?qū)ο?/li> -
S3
用起來(lái)簡(jiǎn)單应又,但是對(duì)于復(fù)雜的對(duì)象關(guān)系宙项,很難高清對(duì)象的意義 - 缺少檢查,
class
屬性可以被任意設(shè)置
1.7 示例
S3
對(duì)象系統(tǒng)廣泛存在于 R
語(yǔ)言的早期開發(fā)中株扛,因此尤筐,在 base
包中包含了許多 S3
對(duì)象。
例如
> ftype(plot)
[1] "S3" "generic"
> ftype(print)
[1] "S3" "generic"
自定義 S3
對(duì)象
say <- function(x, ...) {
UseMethod("say")
}
say.numeric <- function(x, ...) {
paste0("the number is ", x)
}
say.character <- function(x, ...) {
paste0("the character is ", x)
}
使用
> say('nam')
[1] "the character is nam"
> say(12315)
[1] "the number is 12315"