¿Cómo calcular el área de cualquier figura con S3 en R 📊?

Si alguna vez has tenido que escribir código para resolver el mismo problema en diferentes situaciones (como calcular el área de un círculo, un cuadrado y un triángulo), sabes lo fácil que es terminar con un código redundante y difícil de mantener. Pero hay una mejor manera.

Hoy te mostraremos cómo usar el sistema de programación orientada a objetos S3 de R para crear una solución elegante y escalable.


El Problema: Un método para cada figura 🧩

Quizás empezaste definiendo funciones para cada figura, donde cada objeto contenía su propia lógica de cálculo, como en el siguiente ejemplo:

R
# Ejemplo de un enfoque no-S3
Circle <- function(radius) {
  structure(
    list(
      radius = radius,
      get_area = function() {
        return(pi * radius^2)
      }
    ),
    class = "Circle"
  )
}

# Y así para cada figura...

Si bien esto funciona, no es escalable. ¿Qué pasa si añades un pentágono o un hexágono? Tendrías que seguir definiendo nuevas funciones get_area para cada nueva clase.

La verdadera belleza del polimorfismo en R radica en crear una sola función que sepa cómo actuar de manera diferente según el objeto que reciba.


La Solución: Sobrecarga de Métodos con S3 💡

La solución S3 es simple y potente. Se basa en tres componentes:

  1. Constructores: Funciones que crean un objeto y le asignan una clase.

  2. Función Genérica: Una sola función get_area() que es la interfaz pública.

  3. Métodos Específicos: Funciones que tienen la lógica para cada clase (get_area.Circle, get_area.Square, etc.).

Paso 1: Define las Clases y sus Constructores

Primero, creamos funciones que simplemente construyen los objetos. La clave es la función structure(), que crea una lista y le asigna una clase que luego usaremos para la sobrecarga.

R
# Clase para Círculo
Circle <- function(radius) {
  structure(list(radius = radius), class = "Circle")
}

# Clase para Cuadrado
Square <- function(side) {
  structure(list(side = side), class = "Square")
}

# Clase para Triángulo
Triangle <- function(base, height) {
  structure(list(base = base, height = height), class = "Triangle")
}

Paso 2: La Función Genérica get_area()

Aquí es donde entra la magia. La función get_area() es nuestra interfaz principal. El comando UseMethod("get_area") es el que le dice a R que, dependiendo de la clase del objeto que se le pase, debe encontrar y ejecutar el método correcto.

R
# La función genérica. R la usará para "despachar" la llamada.
get_area <- function(x) {
  UseMethod("get_area")
}

Paso 3: Los Métodos Específicos para cada Clase

Ahora, definimos las funciones que realmente hacen el cálculo. S3 las reconoce por su convención de nombres: nombre_generico.nombre_de_clase.

R
# Método para la clase 'Circle'
get_area.Circle <- function(x) {
  return(pi * x$radius^2)
}

# Método para la clase 'Square'
get_area.Square <- function(x) {
  return(x$side^2)
}

# Método para la clase 'Triangle'
get_area.Triangle <- function(x) {
  return(0.5 * x$base * x$height)
}

¡Listo! Ahora puedes calcular el área de cualquier figura simplemente llamando a get_area(), y R se encargará de usar la fórmula correcta.


Verificación de la Lógica con testthat

La mejor manera de confirmar que nuestro código funciona es con pruebas automatizadas. El paquete testthat nos permite hacer esto de forma limpia y legible.

R
# Carga la librería de pruebas
library(testthat)

# Agrupa las pruebas relacionadas
describe("Función genérica get_area()", {

  # Caso de prueba para el círculo
  it("debería calcular correctamente el área de un círculo", {
    circle <- Circle(5)
    expect_equal(get_area(circle), pi * 5^2)
  })

  # Caso de prueba para el cuadrado
  it("debería calcular correctamente el área de un cuadrado", {
    square <- Square(4)
    expect_equal(get_area(square), 16)
  })

  # Caso de prueba para el triángulo
  it("debería calcular correctamente el área de un triángulo", {
    triangle <- Triangle(6, 4)
    expect_equal(get_area(triangle), 12)
  })

  # Un caso de prueba para manejar valores extremos
  it("debería manejar correctamente valores cero", {
    square_zero <- Square(0)
    expect_equal(get_area(square_zero), 0)

    triangle_zero <- Triangle(0, 5)
    expect_equal(get_area(triangle_zero), 0)
  })
})

El código de prueba es fácil de leer, casi como una oración: "Describe el método get_area(), y asegúrate de que debería calcular correctamente el área de un círculo". Es la manera perfecta de garantizar que tu lógica sea robusta.


Conclusión: Código Elegante y Escalable

El enfoque S3 te permite escribir código más limpio, legible y escalable. En lugar de tener múltiples funciones o métodos anidados, tienes una única interfaz (get_area) que funciona para todas tus clases. Esto no solo te ahorra tiempo, sino que también facilita que otros desarrolladores entiendan tu código.

¿Qué otro concepto de tu día a día podrías modelar usando la programación orientada a objetos en R? ¡Déjanos tu idea en los comentarios!

Comentarios

Entradas más populares de este blog

¿Cómo comparar nombres de equipos de distintas fuentes?

¿Cómo hacemos filtrado y extracción de listas anidadas utilizando `purrr`?