Composición VS Herencia

September 20, 2019

...

Vamos a hacer una pequeña introducción a estos dos temas y luego explicaremos los trade-off que tiene cada uno.

Herencia

Partamos por las clases, una clase es el bosquejo de algo que vas a construir, y el objeto representa la construcción del mismo. Para llevarlo a un ejemplo podemos pensar en los planos de una casa, cuando vemos los planos de una casa sabremos como se podría ver esta, nos indica las potenciales medidas, como debiese actuar esta, puertas, etc. Pero no es una casa! Tu no puedes vivir en el plano de una casa. Luego de eso tu tienes las instancias que, en este caso, representan la casa ya construida. El plano tu puedes estudiarlo y realizarle cambios de manera que las siguientes casas que se construyan sean distintas, puedes construir tantas casas como quieras a partir del mismo plano.

El plano es la clase

La casa es el objeto

Un ejemplo:

class Casa {
  puertas = 4
  cochera = 1
  piscina = 0
  alarma = true
  sonarAlarma() {
    console.log('suena la alarma!')
  }
}

const casa1 = new Casa()
const casa2 = new Casa()

Lo bueno que tiene esto es que podemos tomar la estructura de casa una y otra vez para crear objetos en el futuro.

Ahora pensemos que queremos construir un edificio y queremos tomar lo que ya definimos en nuestra clase de casa como base para una clase departamento, ahora tendremos el problema de que los departamentos no tienen piscina (son muy pocos en el mundo los que tienen, pero para este ejemplo veremos los casos cotidianos), solo tienen una puerta de acceso, no tienen cochera pero tienen estacionamiento, sin embargo si tienen alarma (ok... no todos, vamos a presumir que todos los departamentos tienen alarma)

class Home {
  puertas = 1
  alarma = true
  
  sonarAlarma() {
    console.log('suena la alarma!')
  }
}

class Casa extends Home {
  puertas = 4
  cochera = 1
  piscina = 0
}

const casa = new Casa()
casa.sonarAlarma() // suena la alarma!

class Departamento extends Home {
  puertas = 1
}

const departamento = new Departamento()
departamento() // suena la alarma!

La herencia es una técnica utilizada para poder reutilizar nuestro código. En la mayoría de las veces podrás refactorizar el código para separar las clases y así hacer uso de la herencia y tener un beneficio. El problema es que necesitas refactorizar, y algunas veces refactorizar para reutilizar el código no es posible o no hace sentido, vamos a hablar un poco de esto con animales y robots.

Este ejemplo de perro y perro robot lo tomé de MPJ, un YouTuber que se dedica a crear contenido educativo en inglés sobre desarrollo de software.

Tenemos la clase de perro, los perros van al baño, duermen, mueven la cola, entre otras cosas. Tienen muchas cosas en común con otros animales así que crearemos la clase Animal que hará todas esas cosas y la clase perro solo ladrará y morderá.

Luego queremos agregar una nueva clase de perro robot. Este tiene prácticamente lo mismo que nuestro perro pero no tiene sistema digestivo por lo que no puede ir al baño.

Esta situación sucede mucho cuando estas trabajando con herencia, por lo que debes refactorizar el código para que haga sentido. Vamos a ver este mismo ejemplo:

class Animal {
  poop() {}
  comer() {}
}

class Perro extends Animal {
  ladrar() {}
}

// PerroRobot, si extiende de Animal también podrá comer y también ir al baño, por lo que este modelo de herencia no nos sirve para reutilizar nuestro código
class PerroRobot {}

Si aplicamos la herencia sobre PerroRobot y heredamos de Perro tendremos un PerroRobot que puede ir al baño y también que puede comer, los robots no hacen eso! Esto se conoce como el problema del gorila y la banana.

El problema que tiene la herencia es que nos incita a modelar un sistema prediciendo el futuro de este y nos hace pensar que somos lo suficientemente capaces de realizar esta tarea.

El problema del gorila y la banana la explican de la siguiente forma:

Necesitas la clase banana y, como el principio fundamental del software es no repetirse uno mismo, encontramos una clase de gorila que tiene la banana en la mano, y esta hereda del Empire States. Entonces heredamos de la clase banana y terminamos con la banana siendo sujetada por el gorila que se encuentra arriba de un edificio. Esto es un problema ya que no es intuitivo a la hora de desarrollar software, se prestará para confusiones más adelante cuando otro desarrollador intente utilizar tu código.

Ahora, no todo es malo con la herencia, efectivamente existen situaciones donde la herencia puede resultar beneficiosa para tu proyecto como por ejemplo cuando modelas los métodos para guardar, listar, crear y actualizar de una base de datos, estos métodos son estándar y no cambiarán, lo mismo cuando modelas el comportamiento de Functors o Monads. Ya hablaremos sobre estos temas más adelante. Pero lo importante que tienen que saber es que además de los contra que puede traerte, para estructuras definidas que no cambiarán, la herencia puede resultar beneficiosa.

Composición

La composición es quizás uno de mis patrones favoritos y el que menos veo en código productivo, siendo este tan fácil de utilizar! En pocas palabras, composición es el arte de construir en base a piezas más pequeñas.

Las recetas de cocina son un tipo de composición, donde tenemos los ingredientes que vamos a utilizar y con estos construimos un producto final. Pero con esos mismos ingredientes podríamos haber construido otra receta. Todo dependerá de cómo se utilizan los ingredientes.

Las piezas de lego son un muy buen ejemplo para explicar de qué se trata la composición, y es tomar pequeños bloques que, al juntarlos, pueden dar forma a distintas estructuras como castillos, paisajes, casas... nuestra imaginación será el límite. Lo mismo ocurre con la composición de funciones.

En estricto rigor, la composición de funciones es:

Si existe una función:
f(a) => b

Y otra función
g(b) => c

Entonces existe una tercera función:
h(a) => c

Donde h es la composición de f y h

Hay una sección completa en este blog para ver el apartado de composición por lo que no entraremos en más detalle que esto.

Los objetos también pueden componerse, en lugar de heredar de una clase puedes seleccionar los métodos que este tendrá. Vamos a ver un ejemplo con el perro y el perroRobot!

const Pooper = (state) => ({
  poop: () => { state.tanqueDeCaca-- }
})

const Ladrador = (state) => ({
  ladrar: () => { console.log('ladrando!') }
})

const Comelon = (state) => ({
  comer: () => { state.tanqueDeCaca++ }
})

const Perro = (state) => ({
  ...state,
  Pooper(state),
  Ladrador(state),
  Comelon(state),
})

const Robot = (state) => ({
  ...state,
  Ladrador(state),
})

Con este ejemplo podemos componer Todos los tipos de objetos que queramos sin necesidad de refactorizar en un futuro ya que todo se encuentra completamente desacoplado, por lo que podremos ir agregando funcionalidades a nuestros constructores o quitándole sin problema alguno y sin modificar nada entre medio.

El único problema real que podemos ver en la composición es que vas a tener que escribir un poco más de código al componer los objetos

El beneficio es código desacoplado y muy fácil de redactarle pruebas.

Conclusión

Ambos tienen sus pro y sus contra:

La herencia nos permite reutilizar código cuando conocemos de antemano como será nuestra aplicación, esto es sumamente difícil de predecir por lo que es mejor utilizar la herencia con objetos que nosotros sabremos que siempre serán iguales como monads o functors. Sin embargo esto también se puede lograr con composición de objetos.

La composición ya sea de objetos o funciones nos permiten agrupar funcionalidades sin necesidad de conocer el modelo de antemano, por la que hará que nuestro código sea mucho más fácil de reutilizar, sin embargo, como pudieron ver en nuestros ejemplos, debe escribirse un poco más de código.

En lo personal yo prefiero utilizar siempre la composición por sobre la herencia ya que es más fácil crear nuevas funcionalidades y el beneficio de la herencia también se puede obtener con la composición.

Espero que les haya gustado este post, no olviden seguirme en twitter y youtube! Links más abajo.


Suscríbete

Suscríbete a la lista para más cursos, posts y videos tutoriales. Prometo no enviarte más de un correo semanal 🙏

Creado por Nicolás Schürmann ingeniero e instructor de software. Cuando no está programando, esta frente a una cámara dictando cursos, creyéndose youtuber o apoyando a sus alumnos. Puedes seguirlo en twitter o también suscribirte a su canal de youtube. Considera comprar sus cursos por este medio y así apoyas al instructor.