Menu
CoddyTech

闭包作为对象

本课属于 Coddy R 学习旅程的“面向对象编程”单元。第 17 课,共 57 课。

你在 Logic & Flow 中学习过闭包:在另一个函数中创建的函数会保留周围的变量。返回此类函数列表的函数工厂会构建一个对象。它的局部变量是对象的数据,这些函数则是对象的方法:

make_account <- function(balance = 0) {
  deposit <- function(x) balance <<- balance + x
  get_balance <- function() balance
  list(deposit = deposit, get_balance = get_balance)
}
acc <- make_account(100)
acc$deposit(50)
print(acc$get_balance())

输出:

[1] 150

每次调用工厂函数时,都会再次运行它的函数体,因此每个对象都有自己的变量。修改一个账户不会影响另一个账户:

make_account <- function(balance = 0) {
  list(deposit = function(x) balance <<- balance + x,
       get_balance = function() balance)
}
ada <- make_account(100)
bo <- make_account(5)
ada$deposit(1)
cat(ada$get_balance(), bo$get_balance())

输出:

101 5

数据是私有的。balance 不是列表中的元素,因此 acc$balance 的值是 NULL,给它赋值只会添加一个无关的元素。列表中的函数构成了对象完整的公共接口:

make_account <- function(balance = 0) {
  list(get_balance = function() balance)
}
acc <- make_account(100)
print(acc$balance)
acc$balance <- 1e6
print(acc$get_balance())

输出:

NULL
[1] 100

如果这些方法在构建列表之前被定义为局部函数,就可以相互调用。这里,add_many() 重用了 add():

make_tally <- function() {
  total <- 0
  add <- function(x) total <<- total + x
  add_many <- function(xs) for (x in xs) add(x)
  get <- function() total
  list(add = add, add_many = add_many, get = get)
}
t <- make_tally()
t$add(1)
t$add_many(c(2, 3, 4))
print(t$get())

输出:

[1] 10
challenge icon

挑战

简单

完成工厂函数 make_counter(start = 0),位于 Counter.R 中。它会将当前值保存在私有作用域中,并返回一个包含四个函数的列表:increment() 和 decrement() 将值分别加 1 和减 1,reset() 将值重置为 start,value() 返回当前值。

提供的代码会读取起始值,然后读取 +、- 或 reset 命令,并在每条命令后输出当前值。最后,它会输出返回列表中的名称,该列表中只能包含这四个函数。

你的代码写在 Counter.R 中。main.R 包含提供的输入和输出代码,不能编辑。

亲手试试

source("Counter.R")

# 提供的输入/输出代码:保持原样。
input <- suppressWarnings(readLines(file("stdin")))
counter <- make_counter(as.numeric(input[1]))
for (cmd in input[-1]) {
  if (cmd == "+") counter$increment()
  if (cmd == "-") counter$decrement()
  if (cmd == "reset") counter$reset()
  cat(cmd, " -> ", counter$value(), "\n", sep = "")
}
cat("fields visible: ", paste(sort(names(counter)), collapse = ", "), "\n", sep = "")
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: R 在线编译器