2

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

hero
<h6>GUÍA PARA ENTENDER LOS CINCO PRINCIPIOS DE SOLID.</h6>

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énse conoce como SRP por sus siglas en ingles.

Interesante, pero generemos un ejemplo utilizando la vieja confiable Clase Pokémon.

lass Pokemon {
     #name = ""; #type = ""#evolutions = [];

     constructor(name, type, evolutions) {
         this.#name = name;this.#type = type;this.#evolutions = evolutions;
     }

    getname() {
        returnthis.#name;
    }

    gettype() {
        returnthis.#type;
    }

    getevolutions() {
        returnthis.#evolutions;
    }

    saveDateDB(pokemon) { ... }
}

// Creamos la instacia de la clase pokemonconst 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.

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.

classPokemon {
     #name = ""; #type = ""#evolutions = [];

     constructor(name, type, evolutions) {
         this.#name = name;this.#type = type;this.#evolutions = evolutions;
     }

    getname() {
        returnthis.#name;
    }

    gettype() {
        returnthis.#type;
    }

    getevolutions() {
        returnthis.#evolutions;
    }
}

/*
    para aplicar el principio de responsabilidad única
    separamos todas las operaciones que tengan que ver
    con acciones dentro de la base de datos.
*/classDataBase {
    // Método principal
    constructor(pokemon) {}
    saveData() { ... }
    findData() { ... }
    updateData() { ... }
    deleteData(){ ... }
}

// Creamos la instacia de la clase pokemonconst Eevee = new Pokemon("Eevee", "normal", ["Jolteon", "Vaporeon", "Flareon"]);
// pasamos los la instancia del pokemonconst 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 Pokemon**solo se encarga de definir nuevos Pokémons, de esta manera cada Clasetiene una responsabilidad además podemos conseguir una alta cohesión.

<h1>alta cohesión: Se refiere a la media que módulos de un sistema tiene una sola responsabilidad.</h1>

📗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() {
        returnthis.#name;
    }

    gettype() {
        returnthis.#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 pokemonconst 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 pokemonconst 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.

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.

classPokemon{
     #name = ""; 
     #type= ""

     constructor(name, type) {
         this.#name = name;
         this.#type= type;
     }

    get name() {
        returnthis.#name;
    }

    get type() {
        returnthis.#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
*/classTypeElectricextendsPokemon{
    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 ⚡️`;
    }
}

classTypeFireextendsPokemon{
    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 🔥`;
    }
}

classTypeWaterextendsPokemon{
    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.
*/classProcessAttack{
    /*
        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`;
        }, "");
        returnATTACKS;
    }
}

// Creamos una instancia de la clase ProcessAttack 
const Attack = newProcessAttack()

// invocamos al método allPokemonAttack y enviamos las // instancia te cada tipo de pokemon
const MSG = Attack.allPokemonAttack([
    newTypeFire("Flareon"),
    newTypeElectric("Jolteon"),
    newTypeWater("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

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.

<h1>Este …ehm… como te digo que eso no suena tan sencillo como lo imaginaba.</h1>

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.

classPokemon{
     #name = ""; 
     #type= ""

     constructor(name, type) {
         this.#name = name;
         this.#type= type;
     }

    get name() {
        returnthis.#name;
    }

    get type() {
        returnthis.#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
*/classCharmanderextendsPokemon{
    constructor() {
        // Invocamos el contructor de la clase pokemonsuper("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() {
        thrownewError("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
*/classCharizarextendsPokemon{
    constructor() {
        /*
            Invocamos el contructor de la clase pokemon
            inicializamos los valores correspondientes
        */super("Charizar", "Fire");
    }
}

// Creamos la instancia de la clase Charizar
const CHARIZAR = newCharizar();
/*
    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 = newCharmander();
/*
    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),

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.

classPokemon{
     #name = ""; 
     #type= ""

     constructor(name, type) {
         this.#name = name;
         this.#type= type;
     }

    get name() {
        returnthis.#name;
    }

    get type() {
        returnthis.#type;
    }
    // quitamos el método canFly
}

/*
    Generamos una clase PokemonFly la cual
    contara con el método cnFly que tenia la 
    clase Pokemon
*/classPokemonFlyextendsPokemon{
    // Creamos el metodo principal
    constructor(name, type) {
        // Invocamos al constructor de la clase pokémonsuper(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  
*/classCharmanderextendsPokemon{
    // Creamos el metodo principal
    constructor() {
        // Invocamos al constructor de la clase pokémonsuper("Charmander", "Fire");
    }
}

/*
    Creamos una clase Charizar la cual hereda de 
    la clase PkemonFly ya que charizar si utiliza
    el método canFly
*/classCharizarextendsPokemonFly{
    // Creamos el metodo principal
    constructor() {
        // Invocamos al constructor de la clase pokémonsuper("Charizar", "Fire");
    }
}

// Creamos la instancia de la clase Charizar
const CHARIZAR = newCharizar();
/*
    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 = newCharmander();
/*
    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

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.

<h1>Este …ehm… pero en JavaScript no existen las interfaces genio.</h1>

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.

classPokemon{
    #name = "";
    #type= "";

    constructor(name, type) {
        this.#name = name;
        this.#type= type;
    }

    get name() {
        returnthis.#name;
    }

    get type() {
        returnthis.#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.
*/classCharizarextendsPokemon{
    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.
*/classBlastoiseextendsPokemon{
    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 = newCharizar();
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 = newBlastoise();
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.

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 clasepadre, por lo que la herenciamúltiple de manera directa no es posible, para eso usaremos los mix-ins.

classPokemon{
    #name = "";
    #type= "";

    constructor(name, type) {
        this.#name = name;
        this.#type= type;
    }

    get name() {
        returnthis.#name;
    }

    get type() {
        returnthis.#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 => classextendsClasePadre{
    /*
        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 => classextendsClasePadre{
    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) {}
*/classCharizarextendsFly(Pokemon) {
    constructor() {
        super("Charizar", "Fire");
    }
}

/*
    Al utilizar el mixin nuestra clase Blastoise
    toma los métodos y atributos de la clase Swim
    y Pokemon
*/classBlastoiseextendsSwim(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
*/classGengarextendsPokemon{
    constructor() {
        super("Gengar", "Ghost");
    }
}

const CHARIZAR = newCharizar();
console.log(CHARIZAR.canFly);
/*
    output
    Soy Charizar y puedo volar
*/


const BLASTOISE = newBlastoise();
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

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.classApiPokemon {
    getInfo(name) {
        fetch(`https://workshop-mongo.herokuapp.com/pokemon/name/${name}`)
            .then((response) => response.json())
            .then((data) => {
                console.log(data);
        });        
    }
}

// Creamos la clase pokedexclassPokedex {
    #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 pokemonsconst LIST = [
    "Charizar",
    "Eevee",
    "pikachu",
];
// creamos al instancia del pokedexconst 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.

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.
*/classPokedex{
    #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 fetchclassApiPokemon1{
    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 jsonclassApiPokemon2{
    getInfo(name) {
        const DATA = require("./Api.json");
        const INFO = DATA[name] || "";
        console.log(INFO);      
    }
}

// creo mi listado de pokemonsconstLIST = [
    "Charizar",
    "Eevee",
    "pikachu",
];

const API = new ApiPokemon2(); // o puede ser ApiPokemon1();// creamos al instancia del pokedexconst 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

AnteriorDe POO a SOLID explicado con Pokémon - Los cuatro principios de POO

Escribe tu comentario
+ 2