R語言面向對象編程:S3和R6


一、基於S3的面向對象編程

基於S3的面向對象編程是一種基於泛型函數(generic function)的實現方式。

1.S3函數的創建

S3對象組成:generic(generic FUN)+method(generic.class FUN)

泛型函數(generic)創建示例:

get_n_elements <- function(x,...)
{
UseMethod("get_n_elements")
}

通常用UseMethod()函數定義一個泛型函數的名稱,通過傳入參數的class屬性,來確定對應的方法調用。

method(generic.class)函數,創建示例:

 # Create a data.frame method for get_n_elements
get_n_elements.data.frame <- function(x, ...) 
{
nrow(x) * ncol(x) # or prod(dim(x))
}

# Create a default method for get_n_elements
#在使用UseMethod調用時,先在methods中尋找對應class,如果都沒有找到,則會調用#default方法。
get_n_elements.default <- function(x,...)
{
length(unlist(x))
}

methods() 用於查找S3泛型函數中所有可用的methods。

調用pryr包中的is_s3_method() 可以驗證函數是否S3方法。

 

2、S3對象的傳入參數有多個class屬性的處理方法

當變量x具有多個class屬性,應按具體到通用的順序來排列變量對應的class。

使用NextMethod()來調用methods

an_s3_method.some_class <- function(x, ...)
{
# Act on some_class, then
NextMethod("an_s3_method")
}

具體示例如下:

# Assign classes
class(kitty) <- c("cat","mammal","character")

what_am_i <- function(x,...)
{
   UseMethod("what_am_i")
}

# cat method
what_am_i.cat <- function(x)
{
  message("I'm a cat")
  NextMethod("what_am_i")
}

# mammal method
what_am_i.mammal <- function(x, ...)
{
  message("I'm a mammal")
  NextMethod("what_am_i")
}

# character method
what_am_i.character <- function(x, ...)
{
  message("I'm a character vector")
}

 

 

 

二、基於R6的面向對象編程

1、R6對象的創建

首先使用 R6Class() 創建一個class generator(也可叫factory)。

第一個參數是創建的對象的類的名字。
參數private為一個list,為對象保存數據域(data field),包含每個元素的名字。
參數pubilc為一個list,為對象保存面向用戶的函數或功能。

library(R6)
thing_factory <- R6Class( "Thing", private = list( a_field = "a value", another_field = 123 ), public = list( do_something = function(x, y, z) { # Do something here } ) )

創建factory后,再調用new()來生成一個R6對象。new()無需定義,所有的factory都默認具有該方法。

a_thing <- thing_factory$new()

initialize()是一種特殊的公有方法(public method), 在R6對象創建時自動調用,用來設置私域(private field)值。

new()中的參數被傳給initialize()。

# Add an initialize method
microwave_oven_factory <- R6Class(
"MicrowaveOven",
private = list(
power_rating_watts = 800,
door_is_open = FALSE
),
public = list(
cook = function(time_seconds) {
Sys.sleep(time_seconds)
print("Your food is cooked!")
},
open_door = function() {
private$door_is_open = TRUE
},
close_door = function() {
private$door_is_open = FALSE
},
# Add initialize() method here
initialize = function(power_rating_watts,door_is_open){
if(!missing(power_rating_watts)){
private$power_rating_watts<-power_rating_watts
}
if(!missing(door_is_open)){
private$door_is_open<-door_is_open
}
}
)
)

# Make a microwave
a_microwave_oven <- microwave_oven_factory$new(
power_rating_watts = 650,
door_is_open = TRUE
)

  

2.訪問和設置私有域

private組件中的數據對用戶隱藏,這是封裝的原理。使用private$前綴可以訪問私域(private field)。

存在active組件中的active binding(行為像變量的函數),可以獲取和設置私有數據域。

Active bindings是R6中一種特殊的函數調用方式,把對函數的訪問表現為對屬性的訪問,屬於公有組件。

thing_factory <- R6Class(
  "Thing",
  private = list(
    ..a_field = "a value"
  ),
  active = list(
    a_field = function(value) {
      if(missing(value)) {
        private$..a_field
      } else {
        assert_is_a_string(value) # or another assertion
        private$..a_field <- value
      }
    }
  )
)

 將active binding作為數據變量而不是函數進行調用。

 a_thing <- thing_factory$new()

a_thing$a_field # not a_thing$a_field()

 

3、R6的繼承 

(1)子類的創建

繼承將一個類(class)的功能復制到另一個。可在R6Class()中用inherit參數來創建子類。

child_class_factory <- R6Class(
"ChildClass",
inherit = parent_class_factory
)

子類可以添加公有方法來擴展父類的功能,並在公有方法中使用前綴self$調用其他公有方法。

子類也可定義與父類相同的方法名稱來重寫該方法,用於擴展父類的功能,子類使用前綴super$訪問父類的公有方法。

(2)跨級訪問

R6類默認只能使用直接父類的功能。為了跨級訪問,中間類(intermediate classes)首先需要定義一個active binding來顯示父類,形式如下:

active = list(super_ = function() super)

然后可以跨級使用方法parent_method <- super$method()grand_parent_method <- super$super_$method(great_grand_parent_method <- super$super_$super_$method()


(3)共享域


大部分類型的R變量是通過值復制,意思是當復制一個變量時 ,新的變量具有自己的復制值,改變其中一個變量不會影響另外一個。
環境變量(Environments)比較特殊,通過地址傳送來復制(by reference),因此所有的復制品都是等同的,改變其中一個就會改變其他變量。
R6類可利用環境的地址傳送(by reference)復制行為在對象之間共享區域,定義一個名為shared的私域,方式如下:
創建一個新的環境,指定該環境的任意共享域,可通過active bindings訪問。

工作方式與其他active bindings相同,但需使用private$shared$前綴來找回(retrieve)這些區域
R6Class(
"Thing",
private = list(
shared = {
e <- new.env()
e$a_shared_field <- 123
e
}
),
active = list(
a_shared_field = function(value) {
if(missing(value)) {
private$shared$a_shared_field
} else {
private$shared$a_shared_field <- value
}
}
)
)

 

(4)R6對象的復制 

R6對象使用與環境變量相同的地址傳送復制方式,如果使用<- 符號復制R6對象,原對象的變化會影響復制品。
a_reference_copy <- an_r6_object
R6 對象有一個自動生成的clone() 方法,用於值復制,使用clone()復制的對象的變化不影響其他對象。
a_value <- an_r6_object$clone()

當R6對象的一個或多個域包含另一個R6對象時,默認clone() 通過地址傳送復制該R6域。

如果值復制這些R6域,clone() 方法必須使用參數:deep = TRUE。
a_deep_copy <- an_r6_object$clone(deep = TRUE)


(5)R6對象的消除

當消除對象時,定義公有finalize()方法運行自定義代碼。該方法一般用於關閉與數據庫或文件的連接,或者消除例如改變全局選項(option)或者圖形參數的副作用。

thing_factory <- R6Class(
"Thing",
public = list(
initialize = function(x, y, z) {
# do something
},
finalize = function() {
# undo something
# Print a message
"Disconnecting from the cooking times database."
# Disconnect from the database
dbDisconnect(private$conn) } ) ) 

當R的自動垃圾回收器(automated garbage collector)從存儲中清除對象時調用finalize() 方法,可使用gc()強制垃圾回收。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM