Menu
Coddy logo textTech

Patrón Factory

Parte de la sección Programación orientada a objetos del Journey de R de Coddy. Lección 43 de 57.

Una fábrica es una función que crea objetos, de modo que quien la llama no necesita conocer la clase ni su constructor. Recibe una descripción, como un nombre de tipo, y devuelve el objeto correcto:

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

Salida:

circle 12.57 
square 4 

Para un tipo que no reconoce, switch() devuelve NULL. Una fábrica debería detenerse con un mensaje que indique el tipo en su lugar, para que el error se muestre donde se cometió:

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)

Salida:

unknown shape: hexagon

Un registro mantiene los constructores en una lista con nombre, por lo que la fábrica en sí nunca cambia. Aquí, el registro es un entorno, y register_shape() añade un nuevo tipo mientras se ejecuta el programa:

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

Salida:

[1] "rect"
[1] "circle" "rect"  

El código que utiliza los objetos depende únicamente de los genéricos, como area(). Una nueva clase que se registra y tiene un método area() funciona con ese código sin necesidad de modificarlo:

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

Salida:

[1] 10
challenge icon

Desafío

Fácil

Un registro puede exportarse en varios formatos. Exporters.R contiene el export(exporter, record) genérico y el método para csv_exporter, que devuelve los valores unidos por comas. Completa allí los otros dos métodos:

  • json_exporter: {"name": "Ada", "age": "36"};
  • text_exporter: name=Ada; age=36.

Después escribe la fábrica make_exporter(format) en Factory.R: csv, json o text devuelven una lista vacía con la clase csv_exporter, json_exporter o text_exporter, y cualquier otro formato se detiene con unknown format: xml (usa call. = FALSE). El código proporcionado lee los nombres de los campos y los valores, después los nombres de los formatos, e imprime cada exportación o error: y el mensaje.

Tu código va en Exporters.R y Factory.R. main.R contiene el código de entrada/salida proporcionado y no se puede editar.

Pruébalo tú mismo

source("Exporters.R")
source("Factory.R")

# Código de entrada/salida suministrado: déjalo tal como está
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 = "")
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación orientada a objetos

Practica por tu cuenta: Compilador de R online