DEV Community

Cover image for De POO a SOLID explicado con Pokémon - Los cinco principios de SOLID
Jorge Méndez Ortega
Jorge Méndez Ortega

Posted on • Originally published at Medium

13 3

De POO a SOLID explicado con Pokémon - Los cinco principios de SOLID

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.

Fig. 1: Beneficios de SOLID.

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.

Fig.2: El acrónimo de SOLID.

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

Fig. 3: Single responsibility principle o 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);
view raw SolidPokemon.js hosted with ❤ by GitHub

Como podemos apreciar el ejemplo anterior no está aplica SRP ya que la Clase **consta de más de una **responsabilidad.

Fig. 5: Problemas de la clase Pokémon.

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

Fig. 7: 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 🔫
*/
view raw OPokemon❌.js hosted with ❤ by GitHub

Como podemos apreciar el ejemplo anterior no está aplica OCP ya que la Clase ProcessAtack está abierta para recibir nuevos cambios.

Fig. 8: Problemas de la clase ProcessAttack.

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 🔫
*/
view raw OPokemon✅.js hosted with ❤ by GitHub

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

Fig. 10: 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
view raw Lpokemon❌.js hosted with ❤ by GitHub

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),

Fig. 12: Problemas de la Clase Charmander

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
view raw LPokemon✅.js hosted with ❤ by GitHub

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

Fig. 13: 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
*/
view raw PokemonI❌.js hosted with ❤ by GitHub

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.

Fig: 15: Problemas de la clase Pokémon.

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
*/
view raw PokemonI✅.js hosted with ❤ by GitHub

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

Fig., 17: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();
view raw PokedexD❌.js hosted with ❤ by GitHub

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.

Fig. 19: Problemas de la clase Pokedex.

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();
view raw PokedexD✅.js hosted with ❤ by GitHub

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.

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

SurveyJS custom survey software

Build Your Own Forms without Manual Coding

SurveyJS UI libraries let you build a JSON-based form management system that integrates with any backend, giving you full control over your data with no user limits. Includes support for custom question types, skip logic, an integrated CSS editor, PDF export, real-time analytics, and more.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay