Фабричный шаблон
Часть раздела Объектно-ориентированное программирование путешествия по R на Coddy. Урок 43 из 57.
Фабрика — это функция, которая создаёт объекты, поэтому вызывающему коду не нужно знать класс или его конструктор. Она принимает описание, например имя типа, и возвращает подходящий объект:
area <- function(s) UseMethod("area")
area.circle <- function(s) pi * s$r^2
area.square <- function(s) s$side^2
make_shape <- function(type, size) {
switch(type,
circle = structure(list(r = size), class = "circle"),
square = structure(list(side = size), class = "square"))
}
for (t in c("circle", "square")) cat(t, round(area(make_shape(t, 2)), 2), "\n")Вывод:
circle 12.57
square 4 Для неизвестного типа switch() возвращает NULL. Вместо этого фабрика должна завершаться с сообщением, в котором указан тип, чтобы ошибка обнаруживалась там, где была допущена:
make_shape <- function(type, size) {
switch(type,
circle = structure(list(r = size), class = "circle"),
square = structure(list(side = size), class = "square"),
stop("unknown shape: ", type, call. = FALSE))
}
msg <- tryCatch(make_shape("hexagon", 1), error = function(e) conditionMessage(e))
cat(msg)Вывод:
unknown shape: hexagonРеестр хранит конструкторы в именованном списке, поэтому сама фабрика никогда не изменяется. Здесь реестр является окружением, а register_shape() добавляет новый тип во время работы программы:
registry <- new.env()
register_shape <- function(name, maker) assign(name, maker, envir = registry)
make_shape <- function(name, ...) {
if (!exists(name, envir = registry, inherits = FALSE)) stop("unknown shape: ", name, call. = FALSE)
get(name, envir = registry)(...)
}
register_shape("circle", function(r) structure(list(r = r), class = "circle"))
register_shape("rect", function(w, h) structure(list(w = w, h = h), class = "rect"))
print(class(make_shape("rect", 2, 3)))
print(sort(ls(registry)))Результат:
[1] "rect"
[1] "circle" "rect" Код, использующий объекты, зависит только от обобщённых функций, таких как area(). Новый зарегистрированный класс с методом area() работает с этим кодом без каких-либо изменений:
registry <- new.env()
register_shape <- function(name, maker) assign(name, maker, envir = registry)
make_shape <- function(name, ...) get(name, envir = registry)(...)
area <- function(s) UseMethod("area")
total_area <- function(shapes) sum(vapply(shapes, area, numeric(1)))
register_shape("square", function(a) structure(list(a = a), class = "square"))
area.square <- function(s) s$a^2
register_shape("tri", function(b, h) structure(list(b = b, h = h), class = "tri"))
area.tri <- function(s) s$b * s$h / 2
print(total_area(list(make_shape("square", 2), make_shape("tri", 4, 3))))Результат:
[1] 10Задание
ЛегкоЗапись можно экспортировать в нескольких форматах. В Exporters.R находятся общий метод export(exporter, record) и метод для csv_exporter, который возвращает значения, объединённые запятыми. Завершите два других метода в этом файле:
json_exporter:{"name": "Ada", "age": "36"};text_exporter:name=Ada; age=36.
Затем напишите фабрику make_exporter(format) в Factory.R: csv, json или text должны возвращать пустой список с классом csv_exporter, json_exporter или text_exporter, а любой другой формат должен останавливать выполнение с сообщением unknown format: xml (используйте call. = FALSE). Предоставленный код считывает имена полей и значения, затем имена форматов и выводит каждый результат экспорта либо error: и сообщение об ошибке.
Ваш код должен находиться в Exporters.R и Factory.R. В main.R находится предоставленный код ввода/вывода, и его нельзя редактировать.
Попробуйте сами
source("Exporters.R")
source("Factory.R")
# Предоставленный код ввода/вывода: оставьте его как есть
input <- suppressWarnings(readLines(file("stdin")))
fields <- strsplit(input[1], ",")[[1]]
values <- strsplit(input[2], ",")[[1]]
record <- setNames(values, fields)
for (fmt in input[-(1:2)]) {
out <- tryCatch(export(make_exporter(fmt), record), error = function(e) paste("error:", conditionMessage(e)))
cat(out, "\n", sep = "")
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы S3
Работа с файламиСписки с классомФункции-конструкторыМетоды print и formatВалидаторы и вспомогательные функцииПовторение — температуры4Инкапсуляция в R
Замыкания как объектыОкружения как объектыФункции доступаЗащита состоянияПовторение — парковочный счётчик7S4 Наследование
Расширение с помощью containscallNextMethodВиртуальные классыПроверка типов с помощью is()Повторение — смотритель зоопарка10Шаблоны проектирования, часть 2
Шаблон «Декоратор»Шаблон «Строитель»Шаблон «Состояние»Шаблон «Итератор»2Обобщённые функции и методы S3
Обобщённые функции и UseMethodМетоды по умолчаниюМетоды для базовых обобщённых функцийОсновы NextMethodПовторение — площади фигур5Классы S4
Определение классов S4Слоты и доступПроверка корректностиМетоды showПовторение — каталог товаров8Ссылочные классы
Определение ссылочных классовМетоды и обновление полейСемантика копированияНаследование и callSuperПовторение — очередь задач11Проект: Управление библиотекой
Книги и читателиВыдача книг3Наследование S3
Векторы классовПорядок поиска методовРасширение с помощью NextMethodПроверка с помощью inherits()Повторение — расчёт зарплаты сотрудниковПотренируйтесь самостоятельно: Онлайн-компилятор R