Menu
Coddy logo textTech

Funkcje konstruktora

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

Ponieważ R nigdy nie sprawdza pól obiektu S3, każdy obiekt danej klasy powinien być tworzony przez jedną funkcję — jej konstruktor. Zgodnie z konwencją konstruktor niskiego poziomu nazywa się new_ plus nazwa klasy. Przyjmuje każde pole jako argument, sprawdza typy za pomocą stopifnot() i zwraca obiekt:

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)

Wynik:

9.5 EUR

stopifnot() zatrzymuje program, gdy jeden z jego warunków nie jest TRUE, a komunikat wskazuje, który test nie przeszedł. Nieprawidłowy typ jest wykrywany w chwili tworzenia obiektu, a nie później, gdy użyje go jakaś inna funkcja:

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

Wynik:

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

Konstruktor to jedyne miejsce, w którym wywoływana jest funkcja structure() dla klasy, więc każdy obiekt ma te same pola w tej samej kolejności. Kod, który potrzebuje wielu obiektów, wywołuje ją w pętli lub za pomocą 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")

Wynik:

1 1 
2 4 
3 9 

Obiekt S3 nie musi być listą. Konstruktor może nadać etykietę zwykłemu wektorowi i przechowywać dodatkowe informacje jako atrybuty — tak działają wbudowane w R czynniki i daty. attr(x, "unit") odczytuje atrybut:

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)

Wynik:

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

Wyzwanie

Łatwy

Uzupełnij konstruktor new_book(title, author, pages) w pliku Book.R. Sprawdza on za pomocą stopifnot(), czy title i author są typu character, a pages typu numeric, i zwraca listę z tymi trzema polami oraz klasą book. Następnie uzupełnij book_label(b), która zwraca Dune by Frank Herbert, 412 pages.

Dostarczony kod odczytuje wiersze w formacie title;author;pages. Przekazuje pages jako liczbę, jeśli składa się z cyfr, a w przeciwnym razie jako oryginalny tekst, wyświetla book: i etykietę każdego obiektu oraz wyświetla rejected: i tytuł, gdy konstruktor zatrzyma działanie.

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

Spróbuj swoich sił

source("Book.R")

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