Menu
Coddy logo textTech

Singletonパターン

CoddyのRジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 42/57。

デザインパターンとは、何度も繰り返し現れる設計上の問題に対する、実証済みの解決策です。シングルトンパターンは、どこからでもアクセスできるもののインスタンスが常にちょうど1つだけ存在するようにします。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

インスタンスは環境であるため、ある呼び出しを通じて行われた変更は、プログラム内の他のすべての部分から認識されます。異なる場所にある2つの関数が同じログに書き込みます。

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"

インスタンスは最初に必要になったときにのみ作成されます。作成回数を数えると、アクセサーが何度呼び出されても、作成は1回だけ行われることがわかります:

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)

シングルトンは慎重に使用してください。シングルトンはグローバル状態です。どのコードからでも変更でき、リセットしない限り、テストをクリーンなコピーから開始することはできません。ロガーや設定など、プログラムごとに本当に1つだけ存在するものには適していますが、オブジェクトを引数として渡すほうが、多くの場合、より明確です:

# テストしやすくするため: log が渡される
process <- function(order, log) {
  log$lines <- c(log$lines, paste("processing", order$id))
}
challenge icon

チャレンジ

簡単

Logger.R でシングルトンロガーを完成させてください。get_logger() は、最初の呼び出し時に作成される 1 つの環境を返します。この環境には entries フィールド(文字型で、最初は空)が含まれます。次に、以下を記述してください:

  • log_info(msg) と log_error(msg)。共有ロガーのエントリにそれぞれ [INFO] msg または [ERROR] msg を追加します。
  • error_count()。[ERROR] で始まるエントリの数を数えます。

提供されたコードは info,started または error,disk full の行を読み込み、それぞれをログに記録した後、すべてのエントリ、エラー数、および get_logger() を 2 回呼び出したときに同じオブジェクトが返されるかどうかを出力します。

コードは 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オンラインコンパイラ