我是R中面向对象编程的新手,并且正在努力正确编写修改对象的函数。
此示例有效:
store1 <- list(
apples=3,
pears=4,
fruits=7
)
class(store1) <- "fruitstore"
print.fruitstore <- function(x) {
paste(x$apples, "apples and", x$pears, "pears", sep=" ")
}
print(store1)
addApples <- function(x, i) {
x$apples <- x$apples + i
x$fruits <- x$apples + x$pears
return(x)
}
store1 <- addApples(store1, 5)
print(store1)
但我认为应该有一种更简洁的方法来做到这一点而不返回整个对象:
addApples(store1, 5) # Preferable line...
store1 <- addApples(store1, 5) # ...instead of this line
在R中编写修改函数的正确方法是什么? “&LT;&LT; - ”?
更新:谢谢大家在R中成为OOP的Rosetta Stone。非常有用。 我试图解决的问题在流程方面非常复杂,因此引用类的刚性可能会使结构有所帮助。我希望我能接受所有答复,而不仅仅是答案。
答案 0 :(得分:4)
如果您希望自己潜入参考类,可以使用具有替换功能的S3类实际执行此操作。首先,你的例子
store1 <- list(apples=3,pears=4)
class(store1) <- "fruitstore"
print.fruitstore <- function(x) {
x <- paste(unlist(store1), names(store1), collapse=", ")
x <- paste0(x, " for a total of ", sum(unlist(store1)), " fruit.")
NextMethod()
}
store1
# [1] "3 apples, 4 pears for a total of 7 fruit."
请注意如何使用NextMethod
表示我不必print(store1)
,我只需输入store
即可。基本上,一旦我重新分配x
成为我想要在屏幕上显示的内容,我就会调度默认的print
方法。然后:
`addapples<-` <- function(x, ...) UseMethod("addapples<-")
`addapples<-.fruitstore` <- function(x, value) {
x[["apples"]] <- x[["apples"]] + value
x
}
addapples(store1) <- 4
store1
# [1] "7 apples, 4 pears for a total of 11 fruit."
多田!同样,不是典型的S3使用案例。除了[
和[[
函数之外,替换函数通常用于更新属性(例如类,长度等),但我没有看到拉伸它的太多伤害。
请注意,这不是真正的引用分配。实际上,您的fruitstore
对象已被复制,修改并重新分配给原始变量(请参阅R Docs)。
答案 1 :(得分:3)
这是一个参考类实现,正如其中一条评论中所建议的那样。基本思想是设置一个名为Stores
的引用类,它有三个字段:apples
,pears
和fruits
(编辑为访问器方法)。 initialize
方法用于初始化新商店,addApples
方法将商店添加到商店,而show
方法相当于其他对象的print
。
Stores = setRefClass("Stores",
fields = list(
apples = "numeric",
pears = "numeric",
fruits = function(){apples + pears}
),
methods = list(
initialize = function(apples, pears){
apples <<- apples
pears <<- pears
},
addApples = function(i){
apples <<- apples + i
},
show = function(){
cat(apples, "apples and", pears, "pears")
}
)
)
如果我们初始化一个新商店并调用它,这就是我们得到的
FruitStore = Stores$new(apples = 3, pears = 4)
FruitStore
# 3 apples and 4 pears
现在,调用addApples
方法,让我们将4个苹果添加到商店
FruitStore$addApples(4)
FruitStore
# 7 apples and 4 pears
EDIT。根据Hadley的建议,我更新了我的答案,以便fruits
现在成为一种存取方法。随着我们向商店添加更多apples
,它会保持更新状态。谢谢@hadley。
答案 2 :(得分:2)
你应该看一下data.table package。
加载包裹:library(data.table)
定义data.table对象:
store1 <- data.table(apples=3,
pears=4,
fruits=7)
然后定义addApple函数:
addApple <- function(data,i) {data[,apples:=(data[1,apples]+i)];
data[,fruits:=(data[1,apples]+data[1,pears])]}
就是这样。
当你写addApple(store1)
时,你应该得到+ i苹果和“苹果+梨”水果。
如果需要,您仍然可以定义S3类,只需确保它继承data.frame和data.table类。
class(store1) <- c("fruitstore",class(store1))
还有一件事,你应该通过使print明确重写来重写你的S3方法:
print.fruitstore <- function(x) {
print(paste(x$apples, "apples and", x$pears, "pears", sep=" "))
}
或者你可以使用cat:
print.fruitstore <- function(x) {cat(x$apples, "apples and", x$pears, "pears")}
答案 3 :(得分:2)
这是一个使用proto package的实现,它将对象和类统一到原型的单个概念中。例如,Fruitstore
(一个扮演类角色的对象)和一个扮演特定商店角色的对象store1
之间确实没有区别。它们都是原型物。
library(proto)
Fruitstore <- proto(
addApples = function(., i) {
.$apples <- .$apples + i
.$fruits <- .$apples + .$pears
},
print = function(.) cat(.$apples, "apples and", .$pears, "pears\n")
)
# define store1 as a child of Fruitstore
store1 <- Fruitstore$proto(apples = 3, pears = 4, fruits = 7)
store1$addApples(5)
store1$print()