Menu
Coddy logo textTech

Функции-конструкторы

Часть раздела Объектно-ориентированное программирование путешествия по R на Coddy. Урок 3 из 57.

Поскольку R никогда не проверяет поля объекта S3, каждый объект класса должен создаваться одной функцией — его конструктором. По соглашению низкоуровневый конструктор называется new_ плюс имя класса. Он принимает каждое поле в качестве аргумента, проверяет типы с помощью stopifnot() и возвращает объект:

new_money <- function(amount = double(), currency = character()) {
  stopifnot(is.double(amount), is.character(currency))
  structure(list(amount = amount, currency = currency), class = "money")
}
m <- new_money(9.5, "EUR")
cat(m$amount, m$currency)

Результат:

9.5 EUR

stopifnot() останавливает программу, когда одно из его условий не равно TRUE, а сообщение указывает на непрошедшую проверку. Неверный тип обнаруживается в момент создания объекта, а не позже, когда его использует какая-либо другая функция:

new_money <- function(amount = double(), currency = character()) {
  stopifnot(is.double(amount), is.character(currency))
  structure(list(amount = amount, currency = currency), class = "money")
}
m <- new_money("9.5", "EUR")

Вывод:

Error in new_money("9.5", "EUR") : is.double(amount) is not TRUE
Calls: new_money -> stopifnot
Execution halted

Конструктор — единственное место, где для класса вызывается structure(), поэтому у каждого объекта одинаковые поля в одинаковом порядке. Код, которому требуется много объектов, вызывает её в цикле или с помощью lapply():

new_point <- function(x = 0, y = 0) {
  stopifnot(is.numeric(x), is.numeric(y))
  structure(list(x = x, y = y), class = "point")
}
pts <- lapply(1:3, function(i) new_point(i, i * i))
for (p in pts) cat(p$x, p$y, "\n")

Результат:

1 1 
2 4 
3 9 

Объект S3 не обязательно должен быть списком. Конструктор может пометить обычный вектор и сохранить дополнительные сведения в качестве атрибутов — именно так работают собственные факторы и даты в R. attr(x, "unit") считывает атрибут:

new_distance <- function(value = double(), unit = "m") {
  stopifnot(is.double(value), is.character(unit))
  structure(value, unit = unit, class = "distance")
}
d <- new_distance(c(1.5, 2), "km")
print(attr(d, "unit"))
print(unclass(d)[2] * 1000)

Результат:

[1] "km"
[1] 2000
challenge icon

Задание

Легко

Завершите конструктор new_book(title, author, pages) в Book.R. Он с помощью stopifnot() проверяет, что title и author имеют тип character, а pages — числовой тип, и возвращает список с этими тремя полями и классом book. Затем завершите book_label(b), которая возвращает Dune by Frank Herbert, 412 pages.

Предоставленный код считывает строки title;author;pages. Он передаёт pages как число, если оно состоит из цифр, а в противном случае — как исходный текст, выводит book: и метку для каждого объекта, а когда конструктор останавливает выполнение, выводит rejected: и название.

Ваш код должен находиться в Book.R. В main.R содержится предоставленный код ввода/вывода, и его нельзя редактировать.

Попробуйте сами

source("Book.R")

# Предоставленный код ввода/вывода: оставьте его как есть
input <- suppressWarnings(readLines(file("stdin")))
for (line in input) {
  p <- strsplit(line, ";")[[1]]
  pages <- if (grepl("^[0-9]+$", p[3])) as.numeric(p[3]) else p[3]
  b <- tryCatch(new_book(p[1], p[2], pages), error = function(e) NULL)
  if (is.null(b)) {
    cat("rejected: ", p[1], "\n", sep = "")
  } else {
    cat(class(b), ": ", book_label(b), "\n", sep = "")
  }
}
quiz iconПроверьте себя

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

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

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