Menu
Coddy logo textTech

Шаблон Singleton

Часть раздела Объектно-ориентированное программирование путешествия по R на Coddy. Урок 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))
}
challenge icon

Задание

Легко

Завершите реализацию логгера-одиночки в Logger.R. get_logger() возвращает одно окружение, создаваемое при первом вызове, с полем entries (тип character, изначально пустое). Затем напишите:

  • 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 = "")
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор R