单例模式
本课属于 Coddy R 学习旅程的“面向对象编程”单元。第 42 课,共 57 课。
设计模式是对反复出现的设计问题的一种经过验证的解决方案。单例模式确保某个对象恰好只有一个实例,并且可以从任何地方访问。在 R 中,访问器函数会在首次调用时创建实例,将其保存在用 local() 创建的变量中,并且每次都返回同一个实例:
get_config <- local({
instance <- NULL
function() {
if (is.null(instance)) {
instance <<- new.env()
instance$theme <- "light"
}
instance
}
})
a <- get_config()
a$theme <- "dark"
b <- get_config()
cat(b$theme, identical(a, b))输出:
dark TRUE实例是一种环境,因此通过一次调用所做的更改会被程序的其他部分看到。两个位于不同位置的函数会写入同一条日志:
get_log <- local({
instance <- NULL
function() {
if (is.null(instance)) {
instance <<- new.env()
instance$lines <- character(0)
}
instance
}
})
page_a <- function() {
log <- get_log()
log$lines <- c(log$lines, "a visited")
}
page_b <- function() {
log <- get_log()
log$lines <- c(log$lines, "b visited")
}
page_a()
page_b()
page_a()
print(get_log()$lines)输出:
[1] "a visited" "b visited" "a visited"只有在首次需要时才会创建实例。统计创建次数可以看出,无论访问器被调用多少次,实例都只会创建一次:
created <- 0
get_db <- local({
instance <- NULL
function() {
if (is.null(instance)) {
created <<- created + 1
instance <<- new.env()
}
instance
}
})
for (i in 1:5) db <- get_db()
cat("created", created, "time(s)")输出:
created 1 time(s)谨慎使用单例。单例是一种全局状态:任何代码都可以修改它,而且不重置它,测试就无法从干净的副本开始。单例适用于真正只需在整个程序中存在一个的对象,例如日志记录器或设置;将对象作为参数传入通常更清晰:
# 更方便测试:将 log 作为参数传入
process <- function(order, log) {
log$lines <- c(log$lines, paste("processing", order$id))
}挑战
简单在 Logger.R 中完成单例日志记录器。get_logger() 返回一个环境,该环境在首次调用时创建,并包含字段 entries(字符型,初始为空)。然后编写:
log_info(msg)和log_error(msg),将[INFO] msg或[ERROR] msg添加到共享日志记录器的条目中;error_count(),统计以[ERROR]开头的条目数。
提供的代码会读取 info,started 或 error,disk full 这样的行,记录每一行,然后打印所有条目、错误数量,以及两次调用 get_logger() 是否返回同一个对象。
你的代码写在 Logger.R 中。main.R 包含提供的输入/输出代码,不能编辑。
亲手试试
source("Logger.R")
# 提供的输入/输出代码:保持原样
input <- suppressWarnings(readLines(file("stdin")))
for (line in input) {
p <- strsplit(line, ",")[[1]]
if (p[1] == "error") log_error(p[2]) else log_info(p[2])
}
for (e in get_logger()$entries) cat(e, "\n", sep = "")
cat("errors: ", error_count(), "\n", sep = "")
cat("one logger: ", identical(get_logger(), get_logger()), "\n", sep = "")
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: R 在线编译器