Menu
CoddyTech

Mẫu Singleton

Thuộc phần Lập trình hướng đối tượng trong hành trình R của Coddy. Bài học 42 trên 57.

Mẫu thiết kế là một giải pháp đã được kiểm chứng cho một vấn đề thiết kế thường xuyên xuất hiện. Mẫu singleton đảm bảo chỉ có đúng một instance của một thứ, có thể truy cập từ bất cứ đâu. Trong R, một hàm truy cập tạo instance ở lần gọi đầu tiên, lưu nó trong một biến được tạo bằng local() và luôn trả về cùng một instance:

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))

Đầu ra:

dark TRUE

Instance là một môi trường, vì vậy mọi thay đổi qua một lần gọi đều được các phần khác trong chương trình nhìn thấy. Hai hàm ở những nơi khác nhau ghi vào cùng một nhật ký:

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)

Đầu ra:

[1] "a visited" "b visited" "a visited"

Đối tượng chỉ được tạo khi cần đến lần đầu tiên. Đếm số lần tạo cho thấy việc này chỉ xảy ra một lần, dù hàm truy cập được gọi bao nhiêu lần:

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)")

Đầu ra:

created 1 time(s)

Hãy dùng singleton một cách tiết chế. Singleton là trạng thái toàn cục: bất kỳ đoạn code nào cũng có thể thay đổi nó, và một bài kiểm thử không thể bắt đầu với bản sao sạch nếu không đặt lại trạng thái đó. Singleton phù hợp với những thứ thực sự chỉ có một trong mỗi chương trình, chẳng hạn như trình ghi nhật ký hoặc phần cài đặt; truyền một đối tượng làm đối số thường rõ ràng hơn:

# Dễ kiểm thử hơn: log được truyền vào
process <- function(order, log) {
  log$lines <- c(log$lines, paste("processing", order$id))
}
challenge icon

Thử thách

Dễ

Hoàn thiện logger singleton trong Logger.R. get_logger() trả về một môi trường, được tạo ở lần gọi đầu tiên, có trường entries (kiểu character, ban đầu rỗng). Sau đó, viết:

  • log_info(msg) và log_error(msg), các hàm này thêm [INFO] msg hoặc [ERROR] msg vào entries của logger dùng chung;
  • error_count(), hàm này đếm các entries bắt đầu bằng [ERROR].

Đoạn code được cung cấp đọc các dòng info,started hoặc error,disk full, ghi log từng dòng, sau đó in ra mọi entry, số lỗi và cho biết hai lần gọi get_logger() có trả về cùng một đối tượng hay không.

Code của bạn nằm trong Logger.R. main.R chứa code đầu vào/đầu ra được cung cấp và bạn không thể chỉnh sửa tệp này.

Tự làm thử

source("Logger.R")

# Đoạn code đầu vào/đầu ra có sẵn: giữ nguyên
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 = "")
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch R online