R
Tutorial R: Sistema R6: clases referenciales y encapsulamiento
Aprende a crear clases R6 en R con encapsulamiento, métodos públicos y privados, y manejo de constructores y referencias para programación avanzada.
Aprende R y certifícateClases R6 con encapsulamiento
El sistema R6 en R introduce un enfoque diferente para la programación orientada a objetos, permitiendo crear clases referenciales con verdadero encapsulamiento. A diferencia de los sistemas S3 y S4 que son más comunes en R, las clases R6 funcionan por referencia en lugar de por valor, lo que significa que los objetos pueden ser modificados en su lugar sin necesidad de reasignación.
Para trabajar con clases R6, primero necesitamos instalar y cargar el paquete R6
:
# Instalación (solo necesario una vez)
install.packages("R6")
# Cargar el paquete
library(R6)
Creación de una clase R6 básica
La estructura básica de una clase R6 se define mediante la función R6Class()
. Veamos un ejemplo sencillo:
Persona <- R6Class(
classname = "Persona",
public = list(
nombre = NULL,
edad = NULL,
initialize = function(nombre, edad) {
self$nombre <- nombre
self$edad <- edad
},
presentarse = function() {
cat("Hola, me llamo", self$nombre, "y tengo", self$edad, "años.\n")
}
)
)
En este ejemplo, hemos creado una clase llamada Persona
con:
- Dos propiedades públicas:
nombre
yedad
- Un constructor (
initialize
) que inicializa estas propiedades - Un método público llamado
presentarse
Para crear una instancia de esta clase y utilizarla:
# Crear una nueva persona
ana <- Persona$new(nombre = "Ana", edad = 28)
# Acceder a propiedades
ana$nombre # "Ana"
ana$edad # 28
# Llamar a un método
ana$presentarse() # "Hola, me llamo Ana y tengo 28 años."
# Modificar una propiedad
ana$edad <- 29
ana$presentarse() # "Hola, me llamo Ana y tengo 29 años."
Encapsulamiento con campos privados
Una de las ventajas principales del sistema R6 es la capacidad de implementar verdadero encapsulamiento mediante campos y métodos privados. Estos elementos solo son accesibles desde dentro de la clase, no desde el exterior.
Veamos cómo implementar una clase con encapsulamiento:
CuentaBancaria <- R6Class(
classname = "CuentaBancaria",
# Campos privados
private = list(
saldo = 0,
historial = list(),
registrar_movimiento = function(tipo, monto) {
movimiento <- list(
fecha = Sys.time(),
tipo = tipo,
monto = monto,
saldo_resultante = private$saldo
)
private$historial <- c(private$historial, list(movimiento))
}
),
# Interfaz pública
public = list(
titular = NULL,
initialize = function(titular, saldo_inicial = 0) {
self$titular <- titular
private$saldo <- saldo_inicial
private$registrar_movimiento("apertura", saldo_inicial)
},
depositar = function(monto) {
if (monto <= 0) {
stop("El monto a depositar debe ser positivo")
}
private$saldo <- private$saldo + monto
private$registrar_movimiento("depósito", monto)
invisible(self)
},
retirar = function(monto) {
if (monto <= 0) {
stop("El monto a retirar debe ser positivo")
}
if (monto > private$saldo) {
stop("Saldo insuficiente")
}
private$saldo <- private$saldo - monto
private$registrar_movimiento("retiro", monto)
invisible(self)
},
consultar_saldo = function() {
return(private$saldo)
},
ver_estado = function() {
cat("Cuenta de:", self$titular, "\n")
cat("Saldo actual:", private$saldo, "\n")
}
)
)
En este ejemplo más elaborado:
- Hemos definido campos privados (
saldo
ehistorial
) que no son accesibles directamente desde fuera de la clase - Creamos un método privado (
registrar_movimiento
) que solo puede ser llamado por otros métodos de la clase - La interfaz pública proporciona métodos controlados para interactuar con los datos privados
Veamos cómo se utiliza esta clase:
# Crear una cuenta
cuenta_maria <- CuentaBancaria$new(titular = "María López", saldo_inicial = 1000)
# Realizar operaciones
cuenta_maria$depositar(500)
cuenta_maria$retirar(200)
# Consultar estado
cuenta_maria$ver_estado() # Muestra titular y saldo actual
# Intentar acceder a un campo privado (generará un error)
# cuenta_maria$saldo # Error: campo 'saldo' no existe
# cuenta_maria$historial # Error: campo 'historial' no existe
# La única forma de conocer el saldo es mediante el método público
saldo_actual <- cuenta_maria$consultar_saldo()
print(saldo_actual) # 1300
Ventajas del encapsulamiento en R6
El encapsulamiento que ofrece R6 proporciona varias ventajas importantes:
- Protección de datos: Los datos internos están protegidos contra modificaciones accidentales o no autorizadas
- Validación centralizada: Podemos implementar reglas de validación en los métodos públicos (como verificar que los montos sean positivos)
- Abstracción: Los usuarios de la clase solo necesitan conocer la interfaz pública, no los detalles de implementación
- Mantenimiento simplificado: Podemos modificar la implementación interna sin afectar el código que utiliza la clase
Encadenamiento de métodos
Otra característica útil de R6 es la posibilidad de encadenar métodos. Esto se logra haciendo que los métodos devuelvan invisible(self)
:
# Encadenamiento de métodos
cuenta_maria$depositar(100)$retirar(50)$ver_estado()
Este patrón permite escribir código más conciso y legible cuando se realizan múltiples operaciones sobre el mismo objeto.
Consideraciones sobre el diseño de clases con encapsulamiento
Al diseñar clases R6 con encapsulamiento, es recomendable seguir estos principios:
- Hacer privado por defecto: Solo exponer como público lo que realmente necesita ser accesible desde fuera
- Proporcionar métodos de acceso controlados en lugar de exponer directamente las propiedades
- Implementar validación en los métodos que modifican el estado interno
- Mantener la coherencia interna utilizando métodos privados para operaciones comunes
El encapsulamiento es uno de los pilares fundamentales de la programación orientada a objetos, y el sistema R6 nos permite implementarlo de manera efectiva en R, proporcionando una estructura más robusta para nuestros programas.
Métodos públicos y privados
En el sistema R6, la distinción entre métodos públicos y métodos privados es fundamental para implementar un buen diseño orientado a objetos. Esta separación permite controlar qué funcionalidades están disponibles para los usuarios de la clase y cuáles permanecen como parte de la implementación interna.
Los métodos públicos forman la interfaz de la clase, mientras que los métodos privados encapsulan la lógica interna que no necesita ser expuesta. Esta organización ayuda a crear código más mantenible y robusto.
Definición de métodos públicos
Los métodos públicos se definen dentro del argumento public
de la función R6Class()
. Estos métodos son accesibles directamente desde cualquier instancia de la clase:
Calculadora <- R6Class(
classname = "Calculadora",
public = list(
valor = 0,
sumar = function(x) {
self$valor <- self$valor + x
invisible(self)
},
restar = function(x) {
self$valor <- self$valor - x
invisible(self)
},
obtener_valor = function() {
return(self$valor)
}
)
)
# Uso de métodos públicos
calc <- Calculadora$new()
calc$sumar(5)
calc$restar(2)
resultado <- calc$obtener_valor()
print(resultado) # 3
En este ejemplo, sumar()
, restar()
y obtener_valor()
son métodos públicos que cualquier usuario de la clase puede invocar.
Definición de métodos privados
Los métodos privados se definen dentro del argumento private
y solo son accesibles desde dentro de la clase, utilizando la referencia private
:
Estadisticas <- R6Class(
classname = "Estadisticas",
private = list(
datos = NULL,
validar_datos = function() {
if (is.null(private$datos) || length(private$datos) == 0) {
stop("No hay datos para analizar")
}
},
calcular_suma = function() {
private$validar_datos()
return(sum(private$datos))
}
),
public = list(
initialize = function(datos = NULL) {
private$datos <- datos
},
agregar_datos = function(nuevos_datos) {
private$datos <- c(private$datos, nuevos_datos)
invisible(self)
},
media = function() {
private$validar_datos()
return(private$calcular_suma() / length(private$datos))
},
suma = function() {
return(private$calcular_suma())
}
)
)
En este ejemplo:
validar_datos()
ycalcular_suma()
son métodos privados que solo pueden ser llamados desde otros métodos de la claseagregar_datos()
,media()
ysuma()
son métodos públicos que forman la interfaz de la clase
Patrones de uso de métodos privados
Los métodos privados son especialmente útiles para implementar varios patrones de diseño:
- Validación centralizada: Crear métodos privados para validar entradas o estados
GestorArchivos <- R6Class(
classname = "GestorArchivos",
private = list(
verificar_archivo = function(ruta) {
if (!file.exists(ruta)) {
stop("El archivo no existe: ", ruta)
}
if (!file.access(ruta, 4) == 0) {
stop("No se puede leer el archivo: ", ruta)
}
}
),
public = list(
leer_texto = function(ruta) {
private$verificar_archivo(ruta)
return(readLines(ruta))
},
leer_datos = function(ruta, header = TRUE) {
private$verificar_archivo(ruta)
return(read.csv(ruta, header = header))
}
)
)
- Lógica compartida: Extraer código común a varios métodos públicos
Inventario <- R6Class(
classname = "Inventario",
private = list(
items = list(),
buscar_item = function(id) {
for (i in seq_along(private$items)) {
if (private$items[[i]]$id == id) {
return(i)
}
}
return(NULL)
}
),
public = list(
agregar = function(id, nombre, cantidad) {
if (!is.null(private$buscar_item(id))) {
stop("Ya existe un item con ese ID")
}
nuevo_item <- list(
id = id,
nombre = nombre,
cantidad = cantidad
)
private$items <- c(private$items, list(nuevo_item))
invisible(self)
},
actualizar_cantidad = function(id, nueva_cantidad) {
indice <- private$buscar_item(id)
if (is.null(indice)) {
stop("Item no encontrado")
}
private$items[[indice]]$cantidad <- nueva_cantidad
invisible(self)
},
obtener_item = function(id) {
indice <- private$buscar_item(id)
if (is.null(indice)) {
return(NULL)
}
return(private$items[[indice]])
}
)
)
Convenciones de nomenclatura
Es recomendable seguir algunas convenciones para nombrar los métodos:
- Usar verbos para los métodos que realizan acciones:
calcular_total()
,validar_entrada()
- Usar sustantivos para los métodos que devuelven valores:
media()
,total()
- Prefijos como
es_
otiene_
para métodos que devuelven valores lógicos:es_valido()
,tiene_elementos()
Producto <- R6Class(
classname = "Producto",
private = list(
precio_base = 0,
descuento = 0,
calcular_precio_final = function() {
return(private$precio_base * (1 - private$descuento))
}
),
public = list(
initialize = function(precio) {
private$precio_base <- precio
},
establecer_descuento = function(porcentaje) {
if (porcentaje < 0 || porcentaje > 1) {
stop("El descuento debe estar entre 0 y 1")
}
private$descuento <- porcentaje
invisible(self)
},
precio = function() {
return(private$calcular_precio_final())
},
tiene_descuento = function() {
return(private$descuento > 0)
}
)
)
Métodos con comportamiento condicional
Los métodos pueden implementar comportamientos condicionales basados en el estado interno del objeto:
Temporizador <- R6Class(
classname = "Temporizador",
private = list(
tiempo_inicio = NULL,
tiempo_fin = NULL,
verificar_iniciado = function() {
if (is.null(private$tiempo_inicio)) {
stop("El temporizador no ha sido iniciado")
}
}
),
public = list(
iniciar = function() {
if (!is.null(private$tiempo_inicio)) {
warning("El temporizador ya estaba iniciado. Reiniciando.")
}
private$tiempo_inicio <- Sys.time()
private$tiempo_fin <- NULL
invisible(self)
},
detener = function() {
private$verificar_iniciado()
if (!is.null(private$tiempo_fin)) {
warning("El temporizador ya estaba detenido")
return(invisible(self))
}
private$tiempo_fin <- Sys.time()
invisible(self)
},
tiempo_transcurrido = function() {
private$verificar_iniciado()
fin <- private$tiempo_fin
if (is.null(fin)) {
fin <- Sys.time()
}
return(difftime(fin, private$tiempo_inicio, units = "secs"))
},
esta_activo = function() {
return(!is.null(private$tiempo_inicio) && is.null(private$tiempo_fin))
}
)
)
Métodos que retornan self para encadenamiento
Como vimos brevemente, podemos implementar métodos encadenables haciendo que devuelvan invisible(self)
:
Texto <- R6Class(
classname = "Texto",
public = list(
contenido = "",
agregar = function(texto) {
self$contenido <- paste0(self$contenido, texto)
invisible(self)
},
agregar_linea = function(texto) {
if (self$contenido != "") {
self$contenido <- paste0(self$contenido, "\n")
}
self$contenido <- paste0(self$contenido, texto)
invisible(self)
},
limpiar = function() {
self$contenido <- ""
invisible(self)
},
mostrar = function() {
cat(self$contenido, "\n")
invisible(self)
}
)
)
# Uso con encadenamiento
documento <- Texto$new()
documento$agregar("Hola ")$agregar("mundo")$agregar_linea("Esto es R6")$mostrar()
Este patrón permite escribir código más fluido y legible, especialmente cuando se realizan múltiples operaciones secuenciales sobre un mismo objeto.
La combinación efectiva de métodos públicos y privados es clave para crear clases R6 bien diseñadas que sean fáciles de usar y mantener.
Constructores y referencias
En el sistema R6, los constructores y el manejo de referencias son elementos fundamentales que lo distinguen de otros sistemas de orientación a objetos en R. Estos conceptos son esenciales para comprender cómo funcionan los objetos R6 y cómo aprovechar su naturaleza mutable.
El constructor initialize
El constructor en R6 es un método especial llamado initialize
que se ejecuta automáticamente cuando creamos una nueva instancia de la clase. Este método permite configurar el estado inicial del objeto:
Rectangulo <- R6Class(
classname = "Rectangulo",
public = list(
ancho = NULL,
alto = NULL,
initialize = function(ancho = 1, alto = 1) {
self$ancho <- ancho
self$alto <- alto
}
)
)
# Creación de instancias usando el constructor
rect1 <- Rectangulo$new(5, 3)
rect2 <- Rectangulo$new(ancho = 10) # alto tomará el valor por defecto (1)
El constructor tiene varias características importantes:
- Puede tener parámetros con valores predeterminados, lo que permite crear objetos con configuraciones parciales
- Utiliza
self$
para referirse a las propiedades del objeto que se está creando - Puede realizar validaciones o cálculos iniciales
Circulo <- R6Class(
classname = "Circulo",
public = list(
radio = NULL,
initialize = function(radio) {
if (radio <= 0) {
stop("El radio debe ser un valor positivo")
}
self$radio <- radio
},
area = function() {
return(pi * self$radio^2)
}
)
)
# El constructor valida los parámetros
circulo <- Circulo$new(5)
# circulo_invalido <- Circulo$new(-2) # Generaría un error
Naturaleza referencial de R6
A diferencia de la mayoría de los objetos en R, que se copian cuando se asignan a nuevas variables, los objetos R6 funcionan por referencia. Esto significa que cuando asignamos un objeto R6 a una nueva variable, ambas variables apuntan al mismo objeto en memoria:
contador <- R6Class(
classname = "Contador",
public = list(
valor = 0,
incrementar = function() {
self$valor <- self$valor + 1
invisible(self)
}
)
)
# Creamos un contador
c1 <- Contador$new()
c1$incrementar()
print(c1$valor) # 1
# Asignamos a una nueva variable
c2 <- c1
c2$incrementar()
# Ambas variables reflejan el cambio
print(c1$valor) # 2
print(c2$valor) # 2
Esta característica tiene implicaciones importantes:
- Los cambios realizados a través de cualquier referencia afectan al mismo objeto
- No es necesario reasignar el objeto después de modificarlo
- Permite implementar patrones de diseño que requieren identidad de objetos
Comparación con comportamiento por valor
Para entender mejor la naturaleza referencial, comparemos con el comportamiento típico de R (por valor):
# Comportamiento normal de R (por valor)
lista_normal <- list(valor = 10)
copia_lista <- lista_normal
copia_lista$valor <- 20
print(lista_normal$valor) # 10 (no cambia)
print(copia_lista$valor) # 20
# Comportamiento de R6 (por referencia)
objeto_r6 <- Contador$new()
objeto_r6$valor <- 10
referencia_r6 <- objeto_r6
referencia_r6$valor <- 20
print(objeto_r6$valor) # 20 (cambia)
print(referencia_r6$valor) # 20
Creación de referencias explícitas
En ocasiones, necesitamos que un objeto R6 mantenga una referencia a otro objeto R6. Esto permite crear relaciones entre objetos:
Autor <- R6Class(
classname = "Autor",
public = list(
nombre = NULL,
initialize = function(nombre) {
self$nombre <- nombre
}
)
)
Libro <- R6Class(
classname = "Libro",
public = list(
titulo = NULL,
autor = NULL, # Referencia a un objeto Autor
initialize = function(titulo, autor) {
self$titulo <- titulo
self$autor <- autor
},
info = function() {
cat(self$titulo, "escrito por", self$autor$nombre, "\n")
}
)
)
# Creamos un autor
garcia_marquez <- Autor$new("Gabriel García Márquez")
# Creamos libros que referencian al mismo autor
libro1 <- Libro$new("Cien años de soledad", garcia_marquez)
libro2 <- Libro$new("El amor en los tiempos del cólera", garcia_marquez)
# Si cambiamos el nombre del autor, se refleja en todos los libros
garcia_marquez$nombre <- "Gabriel García Márquez (Premio Nobel)"
libro1$info() # Muestra el nombre actualizado
libro2$info() # También muestra el nombre actualizado
Constructores personalizados
Además del constructor estándar initialize
, podemos crear métodos de fábrica (factory methods) que actúen como constructores alternativos:
Punto <- R6Class(
classname = "Punto",
public = list(
x = 0,
y = 0,
initialize = function(x = 0, y = 0) {
self$x <- x
self$y <- y
}
)
)
# Añadimos métodos de fábrica como funciones externas
crear_punto_polar <- function(radio, angulo) {
x <- radio * cos(angulo)
y <- radio * sin(angulo)
return(Punto$new(x, y))
}
# Uso del constructor personalizado
p <- crear_punto_polar(5, pi/4)
cat("Coordenadas:", p$x, p$y, "\n")
También podemos implementar métodos de fábrica como métodos estáticos dentro de la clase:
Fecha <- R6Class(
classname = "Fecha",
public = list(
dia = NULL,
mes = NULL,
anio = NULL,
initialize = function(dia, mes, anio) {
self$dia <- dia
self$mes <- mes
self$anio <- anio
},
formato = function() {
return(sprintf("%02d/%02d/%04d", self$dia, self$mes, self$anio))
}
)
)
# Añadimos un método de fábrica como atributo de la clase
Fecha$hoy <- function() {
fecha_actual <- as.POSIXlt(Sys.Date())
return(Fecha$new(
fecha_actual$mday,
fecha_actual$mon + 1, # En POSIXlt los meses van de 0 a 11
fecha_actual$year + 1900 # En POSIXlt los años son relativos a 1900
))
}
# Uso del método de fábrica
fecha_actual <- Fecha$hoy()
cat("Hoy es:", fecha_actual$formato(), "\n")
Manejo de referencias circulares
En ocasiones, podemos necesitar que dos objetos se referencien mutuamente. Esto crea una referencia circular que debemos manejar con cuidado:
Departamento <- R6Class("Departamento") # Declaración adelantada
Empleado <- R6Class(
classname = "Empleado",
public = list(
nombre = NULL,
departamento = NULL,
initialize = function(nombre) {
self$nombre <- nombre
},
asignar_departamento = function(depto) {
self$departamento <- depto
depto$agregar_empleado(self)
invisible(self)
}
)
)
# Completamos la definición de Departamento
Departamento <- R6Class(
classname = "Departamento",
public = list(
nombre = NULL,
empleados = NULL,
initialize = function(nombre) {
self$nombre <- nombre
self$empleados <- list()
},
agregar_empleado = function(emp) {
# Evitamos duplicados
for (e in self$empleados) {
if (identical(e, emp)) return(invisible(self))
}
self$empleados <- c(self$empleados, list(emp))
invisible(self)
}
)
)
# Creamos las instancias
rrhh <- Departamento$new("Recursos Humanos")
juan <- Empleado$new("Juan Pérez")
# Establecemos la referencia circular
juan$asignar_departamento(rrhh)
# Verificamos las referencias
cat("Departamento de Juan:", juan$departamento$nombre, "\n")
cat("Primer empleado de RRHH:", rrhh$empleados[[1]]$nombre, "\n")
Consideraciones sobre el rendimiento
El sistema de referencias de R6 tiene implicaciones en el rendimiento:
- Ventaja: Evita copias innecesarias de objetos grandes, lo que puede mejorar el rendimiento
- Consideración: Las referencias circulares pueden impedir la liberación automática de memoria
# Ejemplo de rendimiento con objetos grandes
crear_matriz_grande <- function(n) {
return(matrix(rnorm(n*n), n, n))
}
ContenedorDatos <- R6Class(
classname = "ContenedorDatos",
public = list(
datos = NULL,
initialize = function(datos) {
self$datos <- datos
},
procesar = function() {
# Operación que modifica los datos in-situ
self$datos <- self$datos * 2
invisible(self)
}
)
)
# Con objetos grandes, la naturaleza referencial es más eficiente
matriz_grande <- crear_matriz_grande(1000)
contenedor <- ContenedorDatos$new(matriz_grande)
contenedor$procesar() # Modifica los datos sin crear copias
Resumen de buenas prácticas
Al trabajar con constructores y referencias en R6:
- Diseña constructores claros con parámetros nombrados y valores predeterminados
- Realiza validaciones en el constructor para garantizar que el objeto comience en un estado válido
- Ten en cuenta la naturaleza referencial al diseñar la interacción entre objetos
- Utiliza métodos de fábrica para proporcionar formas alternativas de crear objetos
- Maneja con cuidado las referencias circulares para evitar problemas de memoria
El sistema de constructores y referencias de R6 proporciona una flexibilidad que permite implementar patrones de diseño orientados a objetos más complejos y eficientes que los sistemas tradicionales de R.
Otros ejercicios de programación de R
Evalúa tus conocimientos de esta lección Sistema R6: clases referenciales y encapsulamiento 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 R6 en R.
- Implementar encapsulamiento mediante campos y métodos privados.
- Diferenciar entre métodos públicos y privados y su uso adecuado.
- Entender el funcionamiento de constructores y la naturaleza referencial de los objetos R6.
- Aplicar buenas prácticas en el diseño de clases R6 y manejo de referencias, incluyendo referencias circulares.