Menu
Coddy logo textTech

Wzorzec Singleton

Część sekcji Programowanie obiektowe ścieżki R w Coddy. Lekcja 42 z 57.

Wzorzec projektowy to sprawdzone rozwiązanie problemu projektowego, który pojawia się wielokrotnie. Wzorzec singleton gwarantuje, że istnieje dokładnie jedna instancja danego obiektu, dostępna z dowolnego miejsca. W R funkcja dostępowa tworzy instancję przy pierwszym wywołaniu, przechowuje ją w zmiennej utworzonej za pomocą local() i za każdym razem zwraca tę samą instancję:

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

Wynik:

dark TRUE

Instancja jest środowiskiem, więc zmiana wprowadzona za pomocą jednego wywołania jest widoczna dla każdej innej części programu. Dwie funkcje w różnych miejscach zapisują do tego samego dziennika:

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)

Wynik:

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

Instancja jest tworzona dopiero wtedy, gdy jest po raz pierwszy potrzebna. Liczenie utworzeń pokazuje, że dzieje się to raz, niezależnie od tego, jak często wywoływany jest akcesor:

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

Wynik:

created 1 time(s)

Używaj singletonów oszczędnie. Singleton to stan globalny: dowolny kod może go zmienić, a test nie może rozpocząć działania na czystej kopii bez jego zresetowania. Sprawdza się w przypadku rzeczy, które rzeczywiście występują w programie tylko raz, takich jak rejestrator lub ustawienia; często jaśniej jest przekazać obiekt jako argument:

# łatwiejsze do testowania: log jest przekazywany jako argument
process <- function(order, log) {
  log$lines <- c(log$lines, paste("processing", order$id))
}
challenge icon

Wyzwanie

Łatwy

Uzupełnij logger singleton w Logger.R. get_logger() zwraca jedno środowisko, tworzone przy pierwszym wywołaniu, z polem entries (znakowym, początkowo pustym). Następnie napisz:

  • log_info(msg) i log_error(msg), które dodają [INFO] msg lub [ERROR] msg do wpisów współdzielonego loggera;
  • error_count(), które zlicza wpisy zaczynające się od [ERROR].

Dostarczony kod odczytuje wiersze info,started lub error,disk full, zapisuje każdy z nich w logu, a następnie wyświetla wszystkie wpisy, liczbę błędów oraz informację, czy dwa wywołania get_logger() zwracają ten sam obiekt.

Umieść swój kod w Logger.R. main.R zawiera dostarczony kod wejścia/wyjścia i nie można go edytować.

Spróbuj swoich sił

source("Logger.R")

# Dostarczony kod wejścia/wyjścia: pozostaw go bez zmian
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 iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator R online