Problemas de herencia
- Duplicación de código en niños
- Complejidad excesiva en la jerarquía de herencia
- Cambiar el comportamiento de los padres puede conducir a errores en los hijos
En este artículo, veremos de qué se tratan estos problemas y cómo podemos resolverlos usando la composición.
el problema con los lenguajes orientados a objetos es que tienen todo este entorno implícito que llevan consigo. Querías una banana pero lo que obtuviste fue un gorila sosteniendo la banana y toda la jungla. - Joe Armstrong, creador de Erlang
Herencia de juegos de rol
Considere el proceso de crear una jerarquía de personajes de juegos de rol. Inicialmente, se requieren dos tipos de personajes: Guerrero y Mago, cada uno de los cuales tiene una cierta cantidad de salud y un nombre. Estas propiedades son públicas y se pueden mover a la clase de carácter principal.
class Character { constructor(name) { this.name = name; this.health = 100; } }
Un guerrero puede atacar, gastando su energía:
class Warrior extends Character { constructor(name) { super(name); this.stamina = 100; } fight() { console.log(`${this.name} takes a mighty swing!`); this.stamina--; } }
Y un mago puede lanzar hechizos que gastan cierta cantidad de maná:
class Mage extends Character { constructor(name) { super(name); this.mana = 100; } cast() { console.log(`${this.name} casts a fireball!`); this.mana--; } }
Problema de clase de paladín
Ahora, presentemos una nueva clase, Paladín . Un paladín puede luchar y lanzar hechizos. ¿Cómo podemos solucionar esto? Aquí hay un par de soluciones que comparten la misma falta de elegancia:
- Podemos hacer que Paladin sea un descendiente de Character e implementar los métodos
fight()
ycast()
desde cero. En este caso, se viola el principio DRY porque cada uno de los métodos se duplicará en el momento de la creación y necesitará una sincronización constante con los métodos de las clases Mage y Fighter para realizar un seguimiento de los cambios.
- Los métodos
fight()
ycast()
se pueden implementar en el nivel de clase de personaje para que los tres tipos de personajes los tengan. Esta es una solución un poco mejor, pero en este caso, el desarrollador debe anular el métodofight()
para el mago y el método cast() para el guerrero, reemplazándolos con métodos vacíos o consolando un error.
Composición
Estos problemas se pueden resolver con un enfoque funcional utilizando la composición. Basta partir no de sus tipos, sino de sus funciones. Básicamente, tenemos dos características clave que determinan las habilidades de los personajes: la habilidad para luchar y la habilidad para lanzar hechizos.
Estas características se pueden configurar mediante funciones de fábrica que amplían el estado que define el carácter:
const canCast = (state) => ({ cast: (spell) => { console.log(`${state.name} casts ${spell}!`); state.mana--; } }) const canFight = (state) => ({ fight: () => { console.log(`${state.name} slashes at the foe!`); state.stamina--; } })
Así, un personaje se define por un conjunto de estas habilidades y propiedades iniciales, tanto generales (nombre y salud) como privadas (resistencia y maná):
const fighter = (name) => { let state = { name, health: 100, stamina: 100 } return Object.assign(state, canFight(state)); } const mage = (name) => { let state = { name, health: 100, mana: 100 } return Object.assign(state, canCast(state)); } const paladin = (name) => { let state = { name, health: 100, mana: 100, stamina: 100 } return Object.assign(state, canCast(state), canFight(state)); }
Conclusión
Con la composición, puede evitar problemas de herencia usando la composición, y javascript es el lenguaje perfecto para hacerlo.