R
Tutorial R: Sistema S3: clases implícitas y métodos genéricos
Aprende a crear clases S3 y métodos genéricos en R para programación orientada a objetos flexible y polimórfica con ejemplos prácticos.
Aprende R y certifícateCreación de clases S3 con atributos
El sistema S3 es el mecanismo más sencillo y ampliamente utilizado para implementar programación orientada a objetos en R. A diferencia de otros lenguajes como Java o Python, R no tiene una sintaxis formal para definir clases. En su lugar, el sistema S3 utiliza un enfoque más flexible y menos estructurado.
En S3, una "clase" es simplemente una lista de R con un atributo especial llamado "class". Este enfoque minimalista hace que crear objetos S3 sea muy accesible, incluso para programadores que están comenzando con R.
Estructura básica de una clase S3
Para crear una clase S3, seguimos estos pasos fundamentales:
- Crear una lista con los atributos (elementos) que queremos que tenga nuestro objeto
- Asignar un nombre de clase mediante el atributo
class
La sintaxis básica es la siguiente:
# Creación de un objeto S3
mi_objeto <- list(
atributo1 = valor1,
atributo2 = valor2,
...
)
class(mi_objeto) <- "nombre_clase"
Veamos un ejemplo concreto creando una clase persona
:
# Creando un objeto de clase "persona"
ana <- list(
nombre = "Ana",
edad = 28,
profesion = "Estadística"
)
class(ana) <- "persona"
# Verificamos la clase del objeto
class(ana) # [1] "persona"
En este ejemplo, hemos creado un objeto llamado ana
que pertenece a la clase persona
. El objeto contiene tres atributos: nombre, edad y profesión.
Funciones constructoras
Aunque podemos crear objetos S3 directamente como se mostró arriba, es una buena práctica definir una función constructora. Esto nos permite:
- Estandarizar la creación de objetos
- Validar los datos de entrada
- Establecer valores predeterminados
Veamos cómo crear una función constructora para nuestra clase persona
:
# Función constructora para la clase "persona"
crear_persona <- function(nombre, edad, profesion = "No especificada") {
# Validación básica
if (!is.character(nombre)) {
stop("El nombre debe ser una cadena de texto")
}
if (!is.numeric(edad) || edad < 0) {
stop("La edad debe ser un número positivo")
}
# Crear el objeto
persona <- list(
nombre = nombre,
edad = edad,
profesion = profesion
)
# Asignar la clase
class(persona) <- "persona"
return(persona)
}
# Uso de la función constructora
juan <- crear_persona("Juan", 35, "Programador")
maria <- crear_persona("María", 42)
# Verificamos
juan
# $nombre
# [1] "Juan"
#
# $edad
# [1] 35
#
# $profesion
# [1] "Programador"
#
# attr(,"class")
# [1] "persona"
Nuestra función constructora crear_persona
hace varias cosas importantes:
- Valida los datos de entrada para asegurarse de que son del tipo correcto
- Proporciona un valor predeterminado para el atributo
profesion
- Estandariza la estructura del objeto
- Asigna la clase correcta al objeto
Acceso a los atributos
Podemos acceder a los atributos de un objeto S3 de la misma manera que accedemos a los elementos de una lista en R:
# Acceso a atributos usando $
nombre_juan <- juan$nombre
edad_juan <- juan$edad
# Acceso a atributos usando [[]]
profesion_juan <- juan[["profesion"]]
# Imprimimos los valores
nombre_juan # [1] "Juan"
edad_juan # [1] 35
profesion_juan # [1] "Programador"
Modificación de atributos
También podemos modificar los atributos de un objeto S3 como lo haríamos con una lista:
# Modificar la edad de Juan
juan$edad <- 36
# Añadir un nuevo atributo
juan$ciudad <- "Barcelona"
# Ver el objeto modificado
juan
# $nombre
# [1] "Juan"
#
# $edad
# [1] 36
#
# $profesion
# [1] "Programador"
#
# $ciudad
# [1] "Barcelona"
#
# attr(,"class")
# [1] "persona"
Clases S3 con múltiples clases
Un objeto S3 puede pertenecer a múltiples clases simultáneamente. Esto se logra asignando un vector de caracteres al atributo class
:
# Crear un objeto con múltiples clases
empleado <- crear_persona("Carlos", 45, "Analista de datos")
class(empleado) <- c("empleado", "persona")
# Verificar las clases
class(empleado) # [1] "empleado" "persona"
En este caso, el objeto empleado
pertenece tanto a la clase empleado
como a la clase persona
. El orden de las clases es importante, ya que determina la prioridad en el sistema de dispatching de métodos (que veremos en otra sección).
Verificación de clases
Para verificar si un objeto pertenece a una clase específica, podemos usar la función inherits()
:
# Verificar si un objeto pertenece a una clase
inherits(juan, "persona") # TRUE
inherits(empleado, "persona") # TRUE
inherits(empleado, "empleado") # TRUE
inherits(juan, "empleado") # FALSE
Ejemplo práctico: clase para representar puntos 2D
Veamos un ejemplo más práctico creando una clase para representar puntos en un plano bidimensional:
# Función constructora para la clase "punto2d"
crear_punto2d <- function(x = 0, y = 0) {
# Validación
if (!is.numeric(x) || !is.numeric(y)) {
stop("Las coordenadas deben ser valores numéricos")
}
# Crear el objeto
punto <- list(
x = x,
y = y
)
# Asignar la clase
class(punto) <- "punto2d"
return(punto)
}
# Crear algunos puntos
origen <- crear_punto2d()
p1 <- crear_punto2d(3, 4)
p2 <- crear_punto2d(-1, 2)
# Función para calcular la distancia al origen
distancia_origen <- function(punto) {
if (!inherits(punto, "punto2d")) {
stop("El objeto debe ser de clase 'punto2d'")
}
return(sqrt(punto$x^2 + punto$y^2))
}
# Calcular distancias
distancia_origen(p1) # [1] 5
distancia_origen(p2) # [1] 2.236068
En este ejemplo, hemos creado:
- Una función constructora
crear_punto2d
que valida y crea objetos de la clasepunto2d
- Una función auxiliar
distancia_origen
que calcula la distancia de un punto al origen - La función auxiliar verifica que el objeto sea de la clase correcta antes de operar con él
Atributos adicionales con attr()
Además de los elementos de la lista que representan los atributos principales, podemos añadir atributos adicionales usando la función attr()
:
# Añadir un atributo adicional a un punto
attr(p1, "color") <- "rojo"
attr(p1, "etiqueta") <- "Punto A"
# Ver los atributos
attributes(p1)
# $x
# [1] 3
#
# $y
# [1] 4
#
# $class
# [1] "punto2d"
#
# $color
# [1] "rojo"
#
# $etiqueta
# [1] "Punto A"
# Acceder a un atributo específico
attr(p1, "color") # [1] "rojo"
Los atributos adicionales pueden ser útiles para almacenar metadatos o información complementaria sobre el objeto.
Resumen
Las clases S3 en R son una forma sencilla pero potente de implementar programación orientada a objetos:
- Se basan en listas con un atributo especial
class
- Es recomendable crear funciones constructoras para estandarizar la creación de objetos
- Podemos acceder y modificar los atributos como en cualquier lista
- Un objeto puede pertenecer a múltiples clases
- Podemos añadir atributos adicionales usando la función
attr()
Esta flexibilidad hace que el sistema S3 sea muy accesible para principiantes, a la vez que proporciona una base sólida para desarrollar código orientado a objetos en R.
Implementación de métodos genéricos
Los métodos genéricos son una parte fundamental del sistema S3 en R. Estos métodos permiten que una misma función se comporte de manera diferente según la clase del objeto con el que trabaja, implementando así el concepto de polimorfismo de la programación orientada a objetos.
En R, un método genérico es simplemente una función que examina la clase de su primer argumento y llama a la función específica (método) apropiada para esa clase. Esta capacidad permite escribir código más limpio y extensible.
Estructura de los métodos genéricos
Un método genérico en S3 tiene dos componentes principales:
- La función genérica que sirve como punto de entrada
- Los métodos específicos para cada clase
La convención de nomenclatura para los métodos específicos es:
nombre_funcion.nombre_clase
Por ejemplo, si tenemos una función genérica llamada imprimir
y una clase llamada persona
, el método específico se llamaría imprimir.persona
.
Creando un método genérico desde cero
Veamos cómo crear un método genérico simple para nuestra clase persona
:
# Función genérica
describir <- function(x, ...) {
UseMethod("describir")
}
# Método por defecto
describir.default <- function(x, ...) {
cat("Objeto de clase:", class(x)[1], "\n")
}
# Método específico para la clase "persona"
describir.persona <- function(x, detallado = FALSE, ...) {
cat("Persona:", x$nombre, "\n")
cat("Edad:", x$edad, "años\n")
if (detallado) {
cat("Profesión:", x$profesion, "\n")
}
}
# Probemos con nuestros objetos
ana <- list(nombre = "Ana", edad = 28, profesion = "Estadística")
class(ana) <- "persona"
numeros <- c(1, 2, 3, 4, 5)
describir(ana)
# Persona: Ana
# Edad: 28 años
describir(ana, TRUE)
# Persona: Ana
# Edad: 28 años
# Profesión: Estadística
describir(numeros)
# Objeto de clase: numeric
En este ejemplo:
- Creamos la función genérica
describir
que llama aUseMethod("describir")
- Definimos un método por defecto
describir.default
que se usa cuando no hay un método específico para la clase del objeto - Implementamos un método específico
describir.persona
para objetos de clase "persona"
La función UseMethod()
es la que hace la magia del dispatching: examina la clase del primer argumento y llama al método apropiado.
Utilizando métodos genéricos existentes
R viene con varios métodos genéricos predefinidos que podemos extender para nuestras propias clases. Algunos de los más comunes son:
print()
: Para mostrar un objetosummary()
: Para obtener un resumen de un objetoplot()
: Para visualizar un objeto[
: Para la indexación
Implementemos algunos de estos para nuestra clase punto2d
:
# Primero, recreamos nuestra clase punto2d
crear_punto2d <- function(x = 0, y = 0) {
punto <- list(x = x, y = y)
class(punto) <- "punto2d"
return(punto)
}
# Método print para punto2d
print.punto2d <- function(x, ...) {
cat("Punto en coordenadas (", x$x, ",", x$y, ")\n", sep = "")
invisible(x)
}
# Método summary para punto2d
summary.punto2d <- function(object, ...) {
distancia <- sqrt(object$x^2 + object$y^2)
cuadrante <- ifelse(object$x >= 0,
ifelse(object$y >= 0, 1, 4),
ifelse(object$y >= 0, 2, 3))
cat("Resumen del punto:\n")
cat("Coordenadas: (", object$x, ",", object$y, ")\n", sep = "")
cat("Distancia al origen:", round(distancia, 2), "\n")
cat("Cuadrante:", cuadrante, "\n")
}
# Creamos algunos puntos
p1 <- crear_punto2d(3, 4)
p2 <- crear_punto2d(-2, 5)
# Probamos nuestros métodos
p1 # Usa print.punto2d automáticamente
# Punto en coordenadas (3,4)
summary(p2)
# Resumen del punto:
# Coordenadas: (-2,5)
# Distancia al origen: 5.39
# Cuadrante: 2
Observa que no necesitamos llamar explícitamente a print.punto2d()
o summary.punto2d()
. Al usar print()
o summary()
con un objeto de clase punto2d
, R automáticamente selecciona el método correcto.
Implementando operadores como métodos genéricos
Los operadores en R también pueden ser métodos genéricos. Por ejemplo, podemos implementar el operador +
para nuestra clase punto2d
:
# Método para sumar dos puntos
"+.punto2d" <- function(e1, e2) {
# Si el segundo argumento es otro punto2d
if (inherits(e2, "punto2d")) {
return(crear_punto2d(e1$x + e2$x, e1$y + e2$y))
}
# Si el segundo argumento es un número (escalar)
if (is.numeric(e2) && length(e2) == 1) {
return(crear_punto2d(e1$x + e2, e1$y + e2))
}
stop("No se puede sumar un punto2d con este tipo de objeto")
}
# Probamos la suma de puntos
p1 <- crear_punto2d(3, 4)
p2 <- crear_punto2d(1, 2)
p3 <- p1 + p2
p3
# Punto en coordenadas (4,6)
# Probamos la suma con un escalar
p4 <- p1 + 10
p4
# Punto en coordenadas (13,14)
En este ejemplo, hemos sobrecargado el operador +
para que funcione con objetos de clase punto2d
. Nuestro método maneja dos casos:
- Suma de dos puntos (coordenada a coordenada)
- Suma de un punto y un escalar (añade el escalar a ambas coordenadas)
Métodos genéricos con múltiples argumentos
Aunque el dispatching en S3 se basa principalmente en la clase del primer argumento, podemos manejar diferentes combinaciones de clases dentro de nuestros métodos:
# Creamos una nueva clase para representar vectores
crear_vector2d <- function(x = 0, y = 0) {
vector <- list(x = x, y = y)
class(vector) <- "vector2d"
return(vector)
}
# Método print para vector2d
print.vector2d <- function(x, ...) {
cat("Vector <", x$x, ",", x$y, ">\n", sep = "")
invisible(x)
}
# Método para sumar un punto y un vector
"+.punto2d" <- function(e1, e2) {
# Si el segundo argumento es otro punto2d
if (inherits(e2, "punto2d")) {
return(crear_punto2d(e1$x + e2$x, e1$y + e2$y))
}
# Si el segundo argumento es un vector2d
if (inherits(e2, "vector2d")) {
return(crear_punto2d(e1$x + e2$x, e1$y + e2$y))
}
# Si el segundo argumento es un número (escalar)
if (is.numeric(e2) && length(e2) == 1) {
return(crear_punto2d(e1$x + e2, e1$y + e2))
}
stop("No se puede sumar un punto2d con este tipo de objeto")
}
# Probamos la suma de un punto y un vector
p1 <- crear_punto2d(3, 4)
v1 <- crear_vector2d(2, -1)
p5 <- p1 + v1
p5
# Punto en coordenadas (5,3)
En este ejemplo, hemos ampliado nuestro método +.punto2d
para manejar también objetos de clase vector2d
.
Métodos genéricos para clases múltiples
Recordemos que un objeto S3 puede pertenecer a múltiples clases. Cuando llamamos a un método genérico con un objeto de múltiples clases, R busca un método específico para cada clase en orden:
# Creamos un objeto con múltiples clases
punto_especial <- crear_punto2d(1, 1)
class(punto_especial) <- c("punto_especial", "punto2d")
# Método específico para la nueva clase
print.punto_especial <- function(x, ...) {
cat("¡Punto especial en (", x$x, ",", x$y, ")!\n", sep = "")
invisible(x)
}
# Probamos
punto_especial
# ¡Punto especial en (1,1)!
# Si no existiera print.punto_especial, R usaría print.punto2d
R primero busca print.punto_especial
, y como existe, lo usa. Si no existiera, buscaría print.punto2d
, y así sucesivamente.
Ejemplo práctico: clase para datos estadísticos
Veamos un ejemplo más completo creando una clase para manejar datos estadísticos básicos:
# Función constructora
crear_estadisticas <- function(datos) {
if (!is.numeric(datos)) {
stop("Los datos deben ser numéricos")
}
# Calculamos estadísticas básicas
stats <- list(
datos = datos,
n = length(datos),
media = mean(datos),
mediana = median(datos),
desv_est = sd(datos),
min = min(datos),
max = max(datos)
)
class(stats) <- "estadisticas"
return(stats)
}
# Método print
print.estadisticas <- function(x, ...) {
cat("Estadísticas de", x$n, "observaciones\n")
cat("Media:", round(x$media, 2), "\n")
cat("Mediana:", round(x$mediana, 2), "\n")
cat("Desviación estándar:", round(x$desv_est, 2), "\n")
cat("Rango: [", x$min, ", ", x$max, "]\n", sep = "")
invisible(x)
}
# Método summary (más detallado que print)
summary.estadisticas <- function(object, ...) {
cuartiles <- quantile(object$datos)
cat("RESUMEN ESTADÍSTICO\n")
cat("===================\n")
cat("Número de observaciones:", object$n, "\n\n")
cat("Mínimo:", object$min, "\n")
cat("1er Cuartil:", cuartiles[2], "\n")
cat("Mediana:", object$mediana, "\n")
cat("Media:", round(object$media, 2), "\n")
cat("3er Cuartil:", cuartiles[4], "\n")
cat("Máximo:", object$max, "\n\n")
cat("Desviación estándar:", round(object$desv_est, 2), "\n")
cat("Coeficiente de variación:", round(object$desv_est/object$media, 2), "\n")
}
# Método plot
plot.estadisticas <- function(x, ...) {
par(mfrow = c(1, 2))
hist(x$datos, main = "Histograma", xlab = "Valor", col = "lightblue")
boxplot(x$datos, main = "Diagrama de caja", col = "lightgreen")
par(mfrow = c(1, 1))
}
# Probamos nuestra clase
set.seed(123) # Para reproducibilidad
datos_ejemplo <- rnorm(100, mean = 50, sd = 10)
stats <- crear_estadisticas(datos_ejemplo)
# Imprimimos las estadísticas
stats
# Estadísticas de 100 observaciones
# Media: 50.08
# Mediana: 49.68
# Desviación estándar: 9.3
# Rango: [25.3, 77.08]
# Resumen detallado
summary(stats)
# RESUMEN ESTADÍSTICO
# ===================
# Número de observaciones: 100
#
# Mínimo: 25.30167
# 1er Cuartil: 44.27957
# Mediana: 49.68397
# Media: 50.08
# 3er Cuartil: 56.36864
# Máximo: 77.08747
#
# Desviación estándar: 9.3
# Coeficiente de variación: 0.19
# Visualización
# plot(stats) # Esto generaría un histograma y un diagrama de caja
En este ejemplo completo, hemos:
- Creado una clase
estadisticas
con una función constructora - Implementado tres métodos genéricos:
print
,summary
yplot
- Cada método proporciona una forma diferente de interactuar con nuestros datos
Ventajas de los métodos genéricos
Los métodos genéricos ofrecen varias ventajas:
- Polimorfismo: La misma función puede comportarse de manera diferente según el tipo de objeto
- Extensibilidad: Podemos añadir soporte para nuevas clases sin modificar el código existente
- Consistencia: Los usuarios pueden usar las mismas funciones familiares con diferentes tipos de objetos
- Legibilidad: El código es más limpio y expresivo
Estas características hacen que los métodos genéricos sean una herramienta poderosa para escribir código orientado a objetos en R.
Dispatching y UseMethod()
El dispatching es el mecanismo central que permite al sistema S3 de R implementar el polimorfismo. Este proceso determina qué método específico debe ejecutarse cuando llamamos a una función genérica. El corazón de este mecanismo es la función UseMethod()
, que actúa como un distribuidor de llamadas hacia los métodos apropiados según la clase del objeto.
Funcionamiento básico del dispatching
Cuando llamamos a una función genérica en R, ocurre la siguiente secuencia:
- La función genérica recibe los argumentos
- La función llama a
UseMethod()
con el nombre de la función UseMethod()
examina la clase del primer argumento- Se busca y ejecuta el método específico correspondiente
Veamos un ejemplo sencillo para entender este proceso:
# Definimos una función genérica simple
saludar <- function(x, ...) {
UseMethod("saludar")
}
# Método por defecto
saludar.default <- function(x, ...) {
cat("Hola, objeto desconocido\n")
}
# Método para la clase "character"
saludar.character <- function(x, ...) {
cat("Hola,", x, "\n")
}
# Probamos con diferentes tipos de objetos
saludar("María") # Usa saludar.character
# Hola, María
saludar(42) # Usa saludar.default
# Hola, objeto desconocido
En este ejemplo, cuando llamamos a saludar("María")
, R detecta que el primer argumento es de clase "character" y busca el método saludar.character
. Al encontrarlo, lo ejecuta con los argumentos proporcionados.
Anatomía de UseMethod()
La función UseMethod()
toma dos argumentos:
UseMethod(generic, object)
Donde:
- generic: Es el nombre de la función genérica (como cadena de texto)
- object: Es opcional y especifica el objeto para el dispatching (por defecto es el primer argumento)
En la mayoría de los casos, solo necesitamos proporcionar el primer argumento:
mi_funcion <- function(x, ...) {
UseMethod("mi_funcion")
}
Reglas de búsqueda de métodos
Cuando UseMethod()
busca el método apropiado, sigue estas reglas de prioridad:
- Busca un método para la primera clase del objeto
- Si no lo encuentra, busca para la segunda clase (si existe)
- Continúa con las demás clases en orden
- Si no encuentra ningún método específico, usa el método
.default
- Si no existe un método
.default
, genera un error
Veamos un ejemplo con múltiples clases:
# Creamos un objeto con múltiples clases
obj <- structure(list(), class = c("tipo_a", "tipo_b", "tipo_c"))
# Definimos una función genérica
describir <- function(x, ...) {
UseMethod("describir")
}
# Métodos para diferentes clases
describir.tipo_a <- function(x, ...) {
cat("Soy un objeto de tipo A\n")
}
describir.tipo_b <- function(x, ...) {
cat("Soy un objeto de tipo B\n")
}
describir.default <- function(x, ...) {
cat("Soy un objeto de tipo desconocido\n")
}
# Probamos el dispatching
describir(obj) # Usa describir.tipo_a
# Soy un objeto de tipo A
# Cambiamos el orden de las clases
class(obj) <- c("tipo_c", "tipo_b", "tipo_a")
describir(obj) # Ahora usa describir.tipo_b
# Soy un objeto de tipo B
Este ejemplo muestra claramente cómo el orden de las clases afecta al dispatching. Cuando cambiamos el orden, R busca primero un método para "tipo_c", no lo encuentra, luego busca para "tipo_b" y lo encuentra, por lo que lo ejecuta.
Accediendo al método genérico desde un método específico
A veces, queremos que un método específico realice algunas acciones y luego llame al método de otra clase. Podemos hacerlo con NextMethod()
:
# Función genérica
formatear <- function(x, ...) {
UseMethod("formatear")
}
# Método por defecto
formatear.default <- function(x, ...) {
return(as.character(x))
}
# Método para números
formatear.numeric <- function(x, decimales = 2, ...) {
return(format(round(x, decimales), nsmall = decimales))
}
# Método para una clase personalizada que extiende numeric
crear_porcentaje <- function(valor) {
if (!is.numeric(valor)) stop("El valor debe ser numérico")
class(valor) <- c("porcentaje", "numeric")
return(valor)
}
# Método específico para porcentaje que usa NextMethod()
formatear.porcentaje <- function(x, decimales = 1, ...) {
# Primero obtenemos el formato numérico usando el método para numeric
resultado <- NextMethod("formatear", decimales = decimales)
# Luego añadimos el símbolo de porcentaje
return(paste0(resultado, "%"))
}
# Probamos
num <- 42.567
porcentaje <- crear_porcentaje(42.567)
formatear(num) # "42.57"
formatear(porcentaje) # "42.6%"
En este ejemplo, formatear.porcentaje
llama a NextMethod()
, que ejecuta el método formatear.numeric
(la siguiente clase en la jerarquía). Luego, toma el resultado y le añade el símbolo de porcentaje.
Dispatching interno vs. externo
R tiene dos tipos de dispatching:
- Dispatching interno: Ocurre dentro de funciones primitivas como
[
,+
, etc. - Dispatching externo: El que implementamos con
UseMethod()
La principal diferencia es que el dispatching interno es más eficiente pero menos flexible. Como programadores de R, normalmente trabajamos con el dispatching externo.
Inspección del proceso de dispatching
Para entender mejor cómo funciona el dispatching, podemos usar la función methods()
para ver todos los métodos disponibles para una función genérica:
# Ver todos los métodos para la función print
methods("print")
# Ver el código fuente de un método específico
getS3method("print", "factor")
También podemos usar sloop::s3_dispatch()
del paquete sloop
para ver el proceso de dispatching en acción:
# Necesitamos instalar el paquete sloop
# install.packages("sloop")
library(sloop)
# Ver el proceso de dispatching
s3_dispatch(print(factor(c("a", "b"))))
Ejemplo práctico: Sistema de notificaciones
Veamos un ejemplo más completo creando un sistema simple de notificaciones:
# Función constructora base
crear_notificacion <- function(mensaje, timestamp = Sys.time()) {
notif <- list(
mensaje = mensaje,
timestamp = timestamp
)
class(notif) <- "notificacion"
return(notif)
}
# Funciones constructoras específicas
crear_info <- function(mensaje, timestamp = Sys.time()) {
notif <- crear_notificacion(mensaje, timestamp)
class(notif) <- c("info", "notificacion")
return(notif)
}
crear_advertencia <- function(mensaje, timestamp = Sys.time()) {
notif <- crear_notificacion(mensaje, timestamp)
class(notif) <- c("advertencia", "notificacion")
return(notif)
}
crear_error <- function(mensaje, timestamp = Sys.time()) {
notif <- crear_notificacion(mensaje, timestamp)
class(notif) <- c("error", "notificacion")
return(notif)
}
# Función genérica para mostrar notificaciones
mostrar <- function(x, ...) {
UseMethod("mostrar")
}
# Método por defecto
mostrar.default <- function(x, ...) {
cat("Objeto no mostrable\n")
}
# Método base para notificaciones
mostrar.notificacion <- function(x, ...) {
cat("[", format(x$timestamp, "%H:%M:%S"), "] ", x$mensaje, "\n", sep = "")
}
# Métodos específicos
mostrar.info <- function(x, ...) {
cat("INFO: ")
NextMethod()
}
mostrar.advertencia <- function(x, ...) {
cat("ADVERTENCIA: ")
NextMethod()
}
mostrar.error <- function(x, ...) {
cat("ERROR: ")
NextMethod()
}
# Probamos el sistema
info <- crear_info("El proceso ha iniciado")
adv <- crear_advertencia("Memoria baja")
err <- crear_error("No se pudo conectar a la base de datos")
mostrar(info)
# INFO: [12:34:56] El proceso ha iniciado
mostrar(adv)
# ADVERTENCIA: [12:34:56] Memoria baja
mostrar(err)
# ERROR: [12:34:56] No se pudo conectar a la base de datos
En este ejemplo:
- Creamos una jerarquía de clases para diferentes tipos de notificaciones
- Implementamos una función genérica
mostrar
con métodos específicos - Usamos
NextMethod()
para reutilizar la funcionalidad del método base - El dispatching selecciona automáticamente el método correcto según la clase
Consideraciones sobre el dispatching
Al trabajar con el sistema de dispatching de S3, es importante tener en cuenta:
- El dispatching se basa únicamente en la clase del primer argumento
- El orden de las clases es crucial para determinar qué método se ejecuta
NextMethod()
permite la reutilización de código entre métodos- Es una buena práctica proporcionar un método
.default
para manejar casos inesperados
Ventajas del sistema de dispatching S3
El sistema de dispatching S3 ofrece varias ventajas:
- Simplicidad: Es fácil de entender y usar
- Flexibilidad: Permite extender funcionalidades existentes
- Eficiencia: Tiene una sobrecarga mínima en comparación con sistemas OOP más complejos
- Integración: Funciona perfectamente con el resto del ecosistema de R
Estas características hacen que el sistema S3 sea ideal para muchas aplicaciones en R, especialmente para quienes están comenzando con la programación orientada a objetos.
Limitaciones del dispatching S3
A pesar de sus ventajas, el sistema S3 tiene algunas limitaciones:
- Solo realiza dispatching basado en la clase del primer argumento
- No hay verificación de tipos o validación de argumentos automática
- No hay encapsulación real (los atributos son accesibles directamente)
- No hay mecanismo formal para definir interfaces o contratos
Para aplicaciones más complejas que requieren estas características, R ofrece sistemas más avanzados como S4 y R6, pero estos están fuera del alcance de esta lección.
Otros ejercicios de programación de R
Evalúa tus conocimientos de esta lección Sistema S3: clases implícitas y métodos genéricos con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Todas las lecciones de R
Accede a todas las lecciones de R y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Instalación De R Y Rstudio
Introducción Y Entorno
Introducción A R
Introducción Y Entorno
Operadores
Sintaxis
Estructuras De Datos
Sintaxis
Funciones
Sintaxis
Estructuras De Control Iterativo
Sintaxis
Scopes Y Closures
Sintaxis
Estructuras De Control Condicional
Sintaxis
Funciones Anónimas
Sintaxis
Tipos De Datos Y Variables
Sintaxis
Sistema R6: Clases Referenciales Y Encapsulamiento
Programación Orientada A Objetos
Sistema S4: Clases Formales Y Validación
Programación Orientada A Objetos
Herencia Y Polimorfismo En R
Programación Orientada A Objetos
Sistemas De Oop En R
Programación Orientada A Objetos
Sistema S3: Clases Implícitas Y Métodos Genéricos
Programación Orientada A Objetos
Tidyverse Para Transformación De Datos
Manipulación De Datos
Lubridate Para Fechas Y Tiempo
Manipulación De Datos
Group_by Y Summarize Para Agrupación Y Resumen
Manipulación De Datos
Stringr Para Expresiones Regulares
Manipulación De Datos
Tidyr Para Limpieza De Valores Faltantes
Manipulación De Datos
Joins En R Para Combinación Y Relaciones De Tablas
Manipulación De Datos
Pivot_longer Y Pivot_wider Para Reestructuración
Manipulación De Datos
Mutate Y Transmute Para Transformación
Manipulación De Datos
Dplyr Para Filtrado Y Selección
Manipulación De Datos
Readr Y Read.csv Para Importar Datos
Manipulación De Datos
Gráficos Bivariantes En R
Visualización De Datos
Gráficos Univariantes En R
Visualización De Datos
Facetas En Ggplot2
Visualización De Datos
Personalización Y Temas
Visualización De Datos
Ggplot2 Para Visualización De Datos
Visualización De Datos
Gráficos Multivariantes En R
Visualización De Datos
Correlación En R
Estadística
Regresión Lineal En R
Estadística
Pruebas De Hipótesis En R
Estadística
Anova En R
Estadística
Estadística Descriptiva En R
Estadística
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender la estructura y creación de clases S3 mediante listas con atributo 'class'.
- Aprender a definir funciones constructoras para estandarizar y validar objetos S3.
- Implementar y utilizar métodos genéricos y específicos para diferentes clases.
- Entender el mecanismo de dispatching con UseMethod() y la jerarquía de clases en S3.
- Aplicar la sobrecarga de operadores y el uso de NextMethod() para reutilizar métodos.