GUÍA PARA ENTENDER LOS CINCO PRINCIPIOS DE SOLID.
JavaScript — Los cinco principios de SOLID
Genial estamos por terminar nuestra aventura Pokémon, finalmente veremos los cinco principios de SOLID, para poder llegar hasta este punto fue necesario pasar los siguientes retos.
📕 El paradigma de la Programación Orientada a Objetos
📗 Los cuatro principios de la Programación Orientada a Objetos
Si seguiste el tema desde el primer artículo deja decirte gracias 👏, regresando al tema que nos compete y posible mente la pregunta que te estés realizando al igual que yo es, pero ¿Que son los cinco principios de SOLID?
📝 Nota: Para entender los principios de SOLID es recomendable tener bases de los temas anteriores.
Pero, 🤔 ¿Qué son los principios de SOLID?
Es una serie de cinco reglas o principios que son aplicados en la POO cuyas iniciales dan como resultado el acrónimo SOLID este nombre es definido por Michael Feathers el cual hace referencia a los principios definidos por Robert C. Martín (Tío Bob) y Barbara Liskov.
Aplicar y conocer dichos principios tiene como consecuencia un desarrollo.
En términos más generales se puede conseguir un mejor diseño de arquitectura y un código de mayor calidad, cada una de las letras de SOLID hace referencia a uno de sus principios.
Los principios de SOLID no solo son aplicables a POO también son aplicables en Funciones (Programación funcional), pero es muy común verlo aplicado en POO, Estos principios son aplicables a cualquier lenguaje de programación.
🔖 Nota: Todos los ejemplos de código utilizados están basados en JavaScript usando las especificaciones más actuales hasta el momento.
📕S: Single Responsability Principle / Principio de Responsabilidad Única
Una Clase solo debe tener solo una razón para cambiar, esto quiere decir que una clase solo debe contar con una sola responsabilidad.
Si una Clase contara con múltiples responsabilidades esto puede implicar que al realizar un cambio de alguna de ellas puede tener como consecuencia la modificación de otras responsabilidades lo que aumenta la posibilidad de incluir errores y poder afectar otras partes del sistema sin saberlo.
🔖 Nota: Single Responsability Principle **también se conoce como SRP** por sus siglas en ingles.
Interesante, pero generemos un ejemplo utilizando la vieja confiable Clase Pokémon.
class Pokemon { | |
#name = ""; | |
#type = "" | |
#evolutions = []; | |
constructor(name, type, evolutions) { | |
this.#name = name; | |
this.#type = type; | |
this.#evolutions = evolutions; | |
} | |
get name() { | |
return this.#name; | |
} | |
get type() { | |
return this.#type; | |
} | |
get evolutions() { | |
return this.#evolutions; | |
} | |
saveDateDB(pokemon) { ... } | |
} | |
// Creamos la instacia de la clase pokemon | |
const Eevee = new Pokemon("Eevee", "normal", ["Jolteon", "Vaporeon", "Flareon"]); | |
Eeevee.saveDateDB(Eevee); |
Como podemos apreciar el ejemplo anterior no está aplica SRP ya que la Clase **consta de más de una **responsabilidad.
Al contar con múltiples responsabilidades se complica aplicar cambios ya que es posible que insertemos un error porque hacer un cambio de alguna responsabilidad, podría afectar a otras sin que nosotros lo sepamos, es momento de aplicar SRP.
class Pokemon { | |
#name = ""; | |
#type = "" | |
#evolutions = []; | |
constructor(name, type, evolutions) { | |
this.#name = name; | |
this.#type = type; | |
this.#evolutions = evolutions; | |
} | |
get name() { | |
return this.#name; | |
} | |
get type() { | |
return this.#type; | |
} | |
get evolutions() { | |
return this.#evolutions; | |
} | |
} | |
/* | |
para aplicar el principio de responsabilidad única | |
separamos todas las operaciones que tengan que ver | |
con acciones dentro de la base de datos. | |
*/ | |
class DataBase { | |
// Método principal | |
constructor(pokemon) {} | |
saveData() { ... } | |
findData() { ... } | |
updateData() { ... } | |
deleteData(){ ... } | |
} | |
// Creamos la instacia de la clase pokemon | |
const Eevee = new Pokemon("Eevee", "normal", ["Jolteon", "Vaporeon", "Flareon"]); | |
// pasamos los la instancia del pokemon | |
const DB = new DataBase(Eevee); | |
// creamos un nuevo registro | |
DB.saveDateDB(Eevee); | |
Aplicando SRP vemos que entra en juego una nueva clase llamada DataBasela cual es encarga de manipular la Base de datos y por otro lado la Clase Pokemonsolo se encarga de definir nuevos Pokémons, de esta manera cada Clasetiene una responsabilidad además podemos conseguir una alta cohesión.
alta cohesión: Se refiere a la media que módulos de un sistema tiene una sola responsabilidad.
📗O: Open-Closed Principle/ Principio de Abierto-Cerrado
Una entidad de SoftWare tiene que estar abierta para su extensión, pero cerrada para su modificación. Lo que establece este principio es que siempre que se desee realizar un cambio o nueva característica, se tendría que agregar código nuevo en lugar de modificar el existente.
Si una se desea que una Clase realice más funciones, lo ideal es extender las funcionalidades ya existentes y no modificarlas.
🔖 Nota: Open Closed Principle también se conoce como OCP por sus siglas en ingles.
Nuevamente utilizaremos la vieja confiable Clase Pokémon, para generar el ejemplo.
class Pokemon { | |
#name = ""; | |
#type = "" | |
constructor(name, type) { | |
this.#name = name; | |
this.#type = type; | |
} | |
get name() { | |
return this.#name; | |
} | |
get type() { | |
return this.#type; | |
} | |
} | |
/* | |
Se genera una clase para procesar los ataques de | |
los pokemons dependiento del tipo de este. | |
*/ | |
class ProcessAttack { | |
/* | |
Se crea método que permite procesar los ataques | |
de un listado de pokemons. | |
*/ | |
allPokemonAttack(pokemonList) { | |
const ATTACKS = pokemonList.reduce((output, pokemon) => { | |
let attack = ""; | |
const { name, type } = pokemon; | |
/* | |
Se crea un listado de casos que permite asignar | |
un ataque dependiendo el tipo de pokemon. | |
*/ | |
switch(type) { | |
case "Electric": | |
attack = "Impactrueno ⚡️"; | |
break; | |
case "Fire": | |
attack = "Aliento igneo 🔥"; | |
break; | |
case "Water": | |
attack = "Pitsola de agua 🔫"; | |
break; | |
attack = "Ataque base"; | |
default: | |
} | |
return `${output}${name}, ${attack}\n`; | |
}, ""); | |
return ATTACKS; | |
} | |
} | |
// Se crean instancias de la clase pokemon | |
const flareon = new Pokemon("Flareon", "Fire"); | |
const jolteon = new Pokemon("Jolteon", "Electric"); | |
const vaporeon = new Pokemon("Vaporeon", "Water"); | |
// Creamos una instancia de la clase ProcessAttack | |
const Attack = new ProcessAttack() | |
// invocamos al método allPokemonAttack y enviamos las | |
// instancia de la clase pokemon | |
const MSG = Attack.allPokemonAttack([flareon, jolteon, vaporeon]); | |
console.log(MSG); | |
/* | |
Salida | |
Flareon, Aliento igneo 🔥 | |
Jolteon, Impactrueno ⚡️ | |
Vaporeon, Pitsola de agua 🔫 | |
*/ |
Como podemos apreciar el ejemplo anterior no está aplica OCP ya que la Clase ProcessAtack está abierta para recibir nuevos cambios.
Al momento de que nuestra la Clase está abierta a recibir nuevos cambios es posible que insertemos un error, porque estaríamos modificando código ya existente, para aplicar OCP utilizaremos el principio de Herencia y **Polimorfismo.
class Pokemon { | |
#name = ""; | |
#type = "" | |
constructor(name, type) { | |
this.#name = name; | |
this.#type = type; | |
} | |
get name() { | |
return this.#name; | |
} | |
get type() { | |
return this.#type; | |
} | |
/* | |
Cargamos el metodo attack para que todas | |
las clases que hereden de pokemon puedan utilizarlo | |
*/ | |
get attack() { | |
const { name } = this; | |
return `${name}, Ataque base`; | |
} | |
} | |
/* | |
Por cada tipo de pokemon crearemos una nueva clase | |
la cual heredara de la clase pokemon | |
*/ | |
class TypeElectric extends Pokemon { | |
constructor(name) { | |
/* | |
Invocamos el constructor de la clase pokemon | |
y pasamos por defecto el tipo Electric | |
*/ | |
super(name, "Electric"); | |
} | |
get attack() { | |
const { name } = this; | |
return `${name}, Impactrueno ⚡️`; | |
} | |
} | |
class TypeFire extends Pokemon { | |
constructor(name) { | |
/* | |
Invocamos el constructor de la clase pokemon | |
y pasamos por defecto el tipo Fire | |
*/ | |
super(name, "Fire"); | |
} | |
get attack() { | |
const { name } = this; | |
return `${name}, Aliento igneo 🔥`; | |
} | |
} | |
class TypeWater extends Pokemon { | |
constructor(name) { | |
/* | |
Invocamos el constructor de la clase pokemon | |
y pasamos por defecto el tipo Water | |
*/ | |
super(name, "Water"); | |
} | |
get attack() { | |
const { name } = this; | |
return `${name}, Pitsola de agua 🔫`; | |
} | |
} | |
/* | |
Se genera una clase para procesar los ataques de | |
los pokemons dependiento del tipo de este. | |
*/ | |
class ProcessAttack { | |
/* | |
Se crea método que permite procesar los ataques | |
de un listado de pokemons. | |
*/ | |
allPokemonAttack(pokemonList) { | |
/* | |
En este caso solo basta con recibir el listado de pokemons | |
para porder ejecutar un ataque, ya que cada elemento del listado | |
cuenta con su propio ataque. | |
*/ | |
const ATTACKS = pokemonList.reduce((output, pokemon) => { | |
let msg = ""; | |
const { attack } = pokemon; | |
return `${output}${attack}\n`; | |
}, ""); | |
return ATTACKS; | |
} | |
} | |
// Creamos una instancia de la clase ProcessAttack | |
const Attack = new ProcessAttack() | |
// invocamos al método allPokemonAttack y enviamos las | |
// instancia te cada tipo de pokemon | |
const MSG = Attack.allPokemonAttack([ | |
new TypeFire("Flareon"), | |
new TypeElectric("Jolteon"), | |
new TypeWater("Vaporeon"), | |
]); | |
console.log(MSG); | |
/* | |
Salida | |
Flareon, Aliento igneo 🔥 | |
Jolteon, Impactrueno ⚡️ | |
Vaporeon, Pitsola de agua 🔫 | |
*/ |
Al aplicar OCP en la Clase ProcessAttack lo primero que vemos es que ya no le importa saber el tipo de Pokémon solo le interesa el resultado del método attack para poder realizar la acción de ataque, con este nuevo diseño para poder agregar nuevos ataques por tipo de Pokémon solo es necesario crear nuevas SubClases de la Clase Pokémon, esto es un diseño mucho más robusto y fácil de extender.
📘 L: Liskov Substitution Principle/Principio de Sustitución de Liskov
Puede que por su nombre asuste un poco 😱, pero en realidad es más sencillo de lo que parece. Este principio lo que dice es, Si S es un subtipo de T, los objetos de tipo T en un programa pueden reemplazarse por objetos de tipo** S** sin alterar ninguna de las propiedades del programa.
Este …ehm… como te digo que eso no suena tan sencillo como lo imaginaba.
De una manera más simple, el principio declara es que una, SubClase (clase hija) debe ser sustituible por su Super Clase (clase padre), si al hacer esto la clase falla estamos violando el principio🤯.
🔖 Nota: Liskov Substitution Principle también se conoce como LSP por sus siglas en ingles.
Nuevamente utilizaremos la vieja confiable Clase Pokémon, para generar el ejemplo.
class Pokemon { | |
#name = ""; | |
#type = "" | |
constructor(name, type) { | |
this.#name = name; | |
this.#type = type; | |
} | |
get name() { | |
return this.#name; | |
} | |
get type() { | |
return this.#type; | |
} | |
/* | |
Agregamos un metodo el cual define la habiliad | |
de volar. | |
*/ | |
get canFly() { | |
return "Puedo volar"; | |
} | |
} | |
/* | |
Creamos una clase Charmander la cual hereda de | |
la clase pokemon | |
*/ | |
class Charmander extends Pokemon { | |
constructor() { | |
// Invocamos el contructor de la clase pokemon | |
super("Charmander", "Fire"); | |
} | |
/* | |
Charmander aun que es pokémon no cuentan | |
con la capacidad de volar, por lo que el método | |
canFly tendra que mostraria un error. | |
*/ | |
get canFly() { | |
throw new Error("No puedo volar"); | |
} | |
} | |
/* | |
Para el caso de la clase Charizar no se necesita | |
mandar una exepcion ya que el si puede utilizar | |
el método canFly | |
*/ | |
class Charizar extends Pokemon { | |
constructor() { | |
/* | |
Invocamos el contructor de la clase pokemon | |
inicializamos los valores correspondientes | |
*/ | |
super("Charizar", "Fire"); | |
} | |
} | |
// Creamos la instancia de la clase Charizar | |
const CHARIZAR = new Charizar(); | |
/* | |
Como se comento con anterioridad charizar puede | |
utilizar todos los métodos de la clase Pokemon. | |
*/ | |
console.log(`Hola soy ${CHARIZAR.name}`); | |
console.log(`Soy de tipo ${CHARIZAR.type}`); | |
console.log(`Ademas ${CHARIZAR.canFly}`); | |
// Creamos la instancia de la clase Charmander | |
const CHARMANDER = new Charmander(); | |
/* | |
Como se comento con anterioridad charmander no puede | |
utilizar todos los métodos de la clase Pokemon. | |
*/ | |
console.log(`Hola soy ${CHARMANDER.name}`); | |
console.log(`Soy de tipo ${CHARMANDER.type}`); | |
console.log(`Ademas ${CHARMANDER.canFly}`); | |
// En esta caso la salida mostrar un error |
El ejemplo anterior está rompiendo el principio LSP ya que como podemos apreciar la Sub-Clase (Charmander) tiene un comportamiento que difiere de la Clase-Padre (Pokémon),
Al momento que una Sub-Clase no puede realizar las mismas acciones que la Clase-Padre esto puede provocar errores, para poder aplicar LSPutilizaremos el principio de Herencia.
class Pokemon { | |
#name = ""; | |
#type = "" | |
constructor(name, type) { | |
this.#name = name; | |
this.#type = type; | |
} | |
get name() { | |
return this.#name; | |
} | |
get type() { | |
return this.#type; | |
} | |
// quitamos el método canFly | |
} | |
/* | |
Generamos una clase PokemonFly la cual | |
contara con el método cnFly que tenia la | |
clase Pokemon | |
*/ | |
class PokemonFly extends Pokemon { | |
// Creamos el metodo principal | |
constructor(name, type) { | |
// Invocamos al constructor de la clase pokémon | |
super(name, type); | |
} | |
/* | |
Agregamos un metodo el cual define la habiliad | |
de volar. | |
*/ | |
get canFly() { | |
return "Puedo volar"; | |
} | |
} | |
/* | |
Creamos una clase Charmander la cual hereda de | |
la clase Pokemon y en esta ocacion no es necesario | |
mandar una exepción ya que no tiene el método canFly | |
*/ | |
class Charmander extends Pokemon { | |
// Creamos el metodo principal | |
constructor() { | |
// Invocamos al constructor de la clase pokémon | |
super("Charmander", "Fire"); | |
} | |
} | |
/* | |
Creamos una clase Charizar la cual hereda de | |
la clase PkemonFly ya que charizar si utiliza | |
el método canFly | |
*/ | |
class Charizar extends PokemonFly { | |
// Creamos el metodo principal | |
constructor() { | |
// Invocamos al constructor de la clase pokémon | |
super("Charizar", "Fire"); | |
} | |
} | |
// Creamos la instancia de la clase Charizar | |
const CHARIZAR = new Charizar(); | |
/* | |
Como se comento con anterioridad charizar puede | |
utilizar todos los métodos de la clase Pokemon. | |
*/ | |
console.log(`Hola soy ${CHARIZAR.name}`); | |
console.log(`Soy de tipo ${CHARIZAR.type}`); | |
console.log(`Ademas ${CHARIZAR.canFly}`); | |
// Creamos la instancia de la clase Charmander | |
const CHARMANDER = new Charmander(); | |
/* | |
Como se comento con anterioridad charmander no puede | |
utilizar todos los métodos de la clase Pokemon. | |
*/ | |
console.log(`Hola soy ${CHARMANDER.name}`); | |
console.log(`Soy de tipo ${CHARMANDER.type}`); | |
console.log(`Ademas ${CHARMANDER.canFly}`); | |
// En esta caso la salida nos mostrara undedined | |
// ya que el método no exite en la clase Charmander | |
Al aplicar LSP entra en juego PokemonFly que hereda de Pokémon y tiene el método canFly, de esta manera podemos definir quién puede volar y quien no, este principio es una advertencia de que el polimorfismo es poderoso, pero no siempre es fácil de aplica correctamente.
📙 I: Interface Segregation Principle/ Principio de Segregación de Interfaces
Los clientes no tienen que verse forzados a depender de interfaces que no utilicen, en otras palabras, cuando un Cliente A depende de una Clase que implementa una interfaz cuya funcionalidad el Cliente A no utilice, pero otros si, él Cliente A estará siendo afectados por los cambios que fuercen otros clientes.
Este principio suena muy similar a SPR ya que ambos están centrados en la cohesión de responsabilidades.
Este …ehm… pero en JavaScript no existen las interfaces genio.
Por lo que este principio no es aplicable estrictamente como otros, lo ideal es implementar pequeñas interfaces simuladas.
🔖 Nota: Interface Segregation Principle también se conoce como ISP por sus siglas en ingles.
Nuevamente utilizaremos la vieja confiable Clase Pokémon, para generar el ejemplo.
class Pokemon { | |
#name = ""; | |
#type = ""; | |
constructor(name, type) { | |
this.#name = name; | |
this.#type = type; | |
} | |
get name() { | |
return this.#name; | |
} | |
get type() { | |
return this.#type; | |
} | |
/* | |
Agregamos dós nuuevos métodos | |
los caules establecen habilidadess que | |
puede tener un pokemon. | |
*/ | |
get canFly() { | |
return "Puedo volar"; | |
} | |
get canSwim() { | |
return "Puedo Nadar"; | |
} | |
} | |
/* | |
Creamos las clasee Charizar que | |
hereda de la clase Pokemoon por lo | |
cual tomara todos los métodos. | |
*/ | |
class Charizar extends Pokemon { | |
constructor() { | |
super("Charizar", "Fire"); | |
} | |
// En este caso Charizar puede volar | |
get canFly() { | |
return `Soy ${this.name} y puedo volar`; | |
} | |
// | |
get canSwim() { | |
return `No puedo nadar por que soy de tipo ${this.type}`; | |
} | |
} | |
/* | |
Creamos las clasee Charizar que | |
hereda de la clase Pokemoon por lo | |
cual tomara todos los métodos. | |
*/ | |
class Blastoise extends Pokemon { | |
constructor() { | |
super("Blastoise", "Water"); | |
} | |
get canFly() { | |
return `No puedo volar por que soy de tipo ${this.type}`; | |
} | |
get canSwim() { | |
return `Soy ${this.name} y puedo nadar`; | |
} | |
} | |
const CHARIZAR = new Charizar(); | |
console.log(CHARIZAR.canFly); | |
console.log(CHARIZAR.canSwim); | |
/* | |
output | |
Soy Charizar y puedo volar | |
No puedo nadar por que soy de tipo Fire | |
*/ | |
const BLASTOISE = new Blastoise(); | |
console.log(BLASTOISE.canFly); | |
console.log(BLASTOISE.canSwim); | |
/* | |
output | |
No puedo volar por que soy de tipo Water | |
Soy Blastoise y puedo nadar | |
*/ |
Como podemos apreciar el ejemplo anterior no está aplica ISP ya que la Clase Pokemon tiene métodos que no son aplicables en todos las SubClaseslas cuales se ven obligadas aplicar acepciones o comportamientos diferentes para los métodos que no utilizan.
Al momento de que nuestra la Clase consta de métodos que pueden o no aplicar a sus descendientes es muy fácil que insertemos errores, la solución para poder implementar ISP es necesario separar el código en pequeñas partes así de cada clase podrá usar los métodos que realmente utilice.
🔖 Nota: En JavaScript solo se puede tener una clase padre, por lo que la herencia múltiple de manera directa no es posible, para eso usaremos los mix-ins.
class Pokemon { | |
#name = ""; | |
#type = ""; | |
constructor(name, type) { | |
this.#name = name; | |
this.#type = type; | |
} | |
get name() { | |
return this.#name; | |
} | |
get type() { | |
return this.#type; | |
} | |
} | |
/* | |
Para simular el uso de una interfaz vamos a crear | |
un mix-in de la siguiente manera | |
const [Nombre] = (clasePadre) => class extends [clasePadre] | |
*/ | |
const Fly = ClasePadre => class extends ClasePadre { | |
/* | |
un mixin puede tener el numero de métodos | |
que necesitemos implementar. | |
*/ | |
get canFly() { | |
return `Soy ${this.name} y puedo volar`; | |
} | |
} | |
/* | |
Se crea el segundo mixin para poder aplicar | |
el método de nadar solo para ciertos pokemons | |
*/ | |
const Swim = ClasePadre => class extends ClasePadre { | |
get canSwim() { | |
return `Soy ${this.name} y puedo nadar`; | |
} | |
} | |
/* | |
para poder utilizar una herencia multiple en | |
Javascript implementaremoos nuestro mix-in de | |
la siguiente manera | |
class [SubClase] extends Mixin(ClasePadre) {} | |
*/ | |
class Charizar extends Fly(Pokemon) { | |
constructor() { | |
super("Charizar", "Fire"); | |
} | |
} | |
/* | |
Al utilizar el mixin nuestra clase Blastoise | |
toma los métodos y atributos de la clase Swim | |
y Pokemon | |
*/ | |
class Blastoise extends Swim(Pokemon) { | |
constructor() { | |
super("Blastoise", "Water"); | |
} | |
} | |
/* | |
Para los casos donde un pokemon no | |
tenga la habillidad de voloar o nadar | |
solo tiene que heredar de Pokemon | |
*/ | |
class Gengar extends Pokemon { | |
constructor() { | |
super("Gengar", "Ghost"); | |
} | |
} | |
const CHARIZAR = new Charizar(); | |
console.log(CHARIZAR.canFly); | |
/* | |
output | |
Soy Charizar y puedo volar | |
*/ | |
const BLASTOISE = new Blastoise(); | |
console.log(BLASTOISE.canSwim); | |
/* | |
output | |
Soy Blastoise y puedo nadar | |
*/ |
Al aplicar ISP entran en juego el manejo de interfaces, pero como sabemos en JavaScript por lo que implementamos Mix-ins con los cuales podremos simular un comportamiento parecido a las interfaces con lo que podremos agregar solo los métodos que realmente necesite nuestra Subclase.
📒 D: Dependency Inversion Principle / Principio de Inversión de Dependencia
Realmente este principio dicta dos puntos importantes los cuales son
Los módulos de alto nivel no deben depender de los módulos de bajo nivel, Ambos tiene que depender de abstracciones.
Las abstracciones no deben depender de los detalles. Los detalles deben depender de abstracciones.
Hay un momento puede que esto no suene tan sencillo inicialmente, pongamos en claro los términos utilizados.
Módulo de Alto Nivel (Clase): Clase con la que se ejecuta una acción utilizando una herramienta
Módulo de Bajo Nivel (Clase): La **herramienta necesaria para ejecutar la acción
Abstracción: Representa la interfaz que conecta a las 2 clases
Detalles: Cómo funciona la herramienta.
🔖 Nota: Dependency Inversion Principle tambiénse conoce como DIP por sus siglas en ingles.
En este ejemplo crearemos una clase llamada Pokedex ya que desde mi punto de vista es como el mejor ejemplo que se presta para la explicación.
// Creamos el client para la solicitud del api. | |
class ApiPokemon { | |
getInfo(name) { | |
fetch(`https://workshop-mongo.herokuapp.com/pokemon/name/${name}`) | |
.then((response) => response.json()) | |
.then((data) => { | |
console.log(data); | |
}); | |
} | |
} | |
// Creamos la clase pokedex | |
class Pokedex { | |
#pokemons = [] | |
constructor(pokemons) { | |
this.#pokemons = pokemons; | |
/* | |
En este punto se a creado a una dependencias especifica | |
sin importar el tipo de dependendencia tendriamos que poder | |
utilizar el método getInfo | |
*/ | |
this.#Api = new ApiPokemon(); | |
} | |
getInfo() { | |
this.#pokemons.forEach(pokemon => { | |
this.Api.getInfo(pokemon); | |
}); | |
} | |
} | |
// creo mi listado de pokemons | |
const LIST = [ | |
"Charizar", | |
"Eevee", | |
"pikachu", | |
]; | |
// creamos al instancia del pokedex | |
const POKEDEX = new Pokedex(LIST); | |
// genero la impeción de la data. | |
POKEDEX.getInfo(); |
Revisando el ejemplo podemos ver que la Clase Pokedex tiene una dependencia directa a ApiPokemon. por esta razón el principio de DIP no se aplica ya que una de las Clases tiene conocimiento de cómo se implementa ApiPokemon.
Para poder implementar DIP utilizaremos inyección de dependencias así la Clase Pokedex solo se encargará de solicitar data.
/* | |
Generamos algunos cambios en la clase | |
con los cuales ya no generamos una dependencia | |
de algun modulo en especifico sino que solo inyectamo | |
la dependencia que utilizaremos. | |
*/ | |
class Pokedex { | |
#pokemons = []; | |
#dependencie = nulll; | |
constructor(pokemons, dependencie) { | |
this.#pokemons = pokemons; | |
this.dependencie = dependencie; | |
} | |
getInfo() { | |
this.#pokemons.forEach(pokemon => { | |
/* | |
Al inyectar la dependenccia solo bastara con utilizar | |
el método que requerimos utilizar. | |
*/ | |
tthis.dependencie.getInfo(pokemon); | |
}); | |
} | |
} | |
// Creamos el client para la solicitud del api. utilizando fetch | |
class ApiPokemon1 { | |
getInfo(name) { | |
fetch(`https://workshop-mongo.herokuapp.com/pokemon/name/${name}`) | |
.then((response) => response.json()) | |
.then((data) => { | |
console.log(data); | |
}); | |
} | |
} | |
// Creamos el client para la solicitud del api. desde un json | |
class ApiPokemon2 { | |
getInfo(name) { | |
const DATA = require("./Api.json"); | |
const INFO = DATA[name] || ""; | |
console.log(INFO); | |
} | |
} | |
// creo mi listado de pokemons | |
const LIST = [ | |
"Charizar", | |
"Eevee", | |
"pikachu", | |
]; | |
const API = new ApiPokemon2(); // o puede ser ApiPokemon1(); | |
// creamos al instancia del pokedex | |
const POKEDEX = new Pokedex(LIST, API); | |
// genero la impreción de la data. | |
POKEDEX.getInfo(); |
Al momento de realizar una inyección de Dependencias la clase Pokedex, eliminamos la dependencia que se tenía de la clase ApiPokemon, de esta manera cumpliríamos con el principio de DIP.
🔖 Nota: La implementación de DIP regular mente puedes se utiliza con interfaces en este caso podríamos haber usado mix-in.
Conclusiones
Como podemos ver cada uno de los principios de SOLID consigue un objetivo en concreto.
Principio de Responsabilidad Única:
Su propósito es separar los comportamientos.Principio Abierto/Cerrado:
Su objetivo es ampliar el comportamiento de una clase sin modificar el comportamiento existente.Principio de Sustitución de Liskov:
**Su objetivo es aplicar coherencia entre clases.Principio de Segregación de la Interfaz:
**Su objetivo es dividir un conjunto de acciones en conjuntos más pequeños para ejecutar solo el conjunto de acciones que se requieren.Principio de inversiones Dependencias:
**Su objetivo es reducir la dependencia de una clase de alto nivel en la clase de bajo nivel mediante la introducción de una interfaz.
Por último, recordemos que SOLID solo es una herramienta que nos ayuda a escribir mejor código, por lo que hay que tomar en cuenta que no hay que caer en el uso excesivo de SOLID ya que puede que estos principios compliquen mucho el código si es así talvez solo sea necesario aplicar parte de estos.
Top comments (0)