Seguimos con nuestro manual de buenas prácticas y los principios S.O.L.I.D. del tito Bob.

Recordad que podéis echar un vistazo a la saga en los siguientes enlaces:

Ya nos vamos encauzando hacia el final y en este caso vamos a indagar el el principio al que hace referencia la letra I, que no es otro que el principio de segregación de la interfaz.

Otro nombrecito complicado, ¡eh! No nos lo ponen fácil.

En su definición oficial, este principio dice (Wikipedia):

Los clientes de un programa dado solo deberían conocer de este aquellos métodos que realmente usan, y no aquellos que no necesitan usar

Nos enteramos sólo regular, ¿verdad? Juanjo, ¡traduce!

Mi interpretación del principio es la siguiente:

Teniendo una interfaz, una clase que implemente dicha interfaz no debería implementarla si esta contiene métodos que no son propios de la clase.

Si todavía no entendéis bien el concepto de interfaz o habéis aterrizado en este artículo sin haber leído los anteriores de la saga, os aconsejo que os vayáis al artículo sobre el principio abierto/cerrado de S.O.L.I.D. (tenéis los enlaces al principio de este artículo), donde se implementa una interfaz que os hará entender el concepto un poco mejor.

Manual_buenas_practicas_principio_segregacion_interfaz

Deciros que este ejemplo ha sido tomado del episodio del podcast de web reactiva: principios S.O.L.I.D para novatos. Bien de patos y vacas para entender los conceptos. Jejeje.

El principio mal implementado

Imaginemos que estamos en una granja en Kentucky y se nos pide que creemos un programa que implemente el calculp la velocidad de los animales en función del tipo de animal.

Lógico, ¡eh!. Pues es altamente instructivo, creedme. No integré este concepto hasta que lo vi en este ejemplo.

Tenemos en cuenta que:

  • Vamos a ceñirnos sólo a dos animales: vacas y patos.
  • Tenemos que integrar una funcionalidad que calcule la velocidad.
  • Hay que tener en cuenta que el cálculo de la velocidad es distinto en el aire que en tierra, ya que implementan factores de cálculo distintos.

Debido a que en todos los animales vamos a integrar el cálculo de la velocidad (sea cual sea el animal, tenemos que suponer que todos se mueven), una de las aproximaciones para solucionar este problema es crear un interfaz que sirva como contrato y en el que se obligue a todas las clases que implementen este interfaz a implementar una serie de métodos comunes a todos los animales.

interface Animal{

    //método que calcula la velocidad en tierra
    public function calculateEarthSpeed(){}

    //método que calcule la velocidad en el aire
    public function calculateAirSpeed(){}
    
}

Ahora podemos declarar las dos clases que implementan este interfaz:

//clase vaca
class Cow implements Animal{

    //método que calcula la velocidad en tierra
    public function calculateEarthSpeed(){
    
        //funcionalidad de cálculo de velocidad en tierra
    
    }

    //método que calcule la velocidad en el aire
    public function calculateAirSpeed(){
    
        //funcionalidad de cálculo de velocidad en aire
    
    }

}

//clase pato
class Duck implements Animal{

    //método que calcula la velocidad en tierra
    public function calculateEarthSpeed(){
    
        //funcionalidad de cálculo de velocidad en tierra
    
    }

    //método que calcule la velocidad en el aire
    public function calculateAirSpeed(){
    
        //funcionalidad de cálculo de velocidad en aire
    
    }

}

Lo veis ya claro, ¿verdad?

Al tener una interfaz, estamos obligando a las clases que implementan dicha interfaz a tener que tener una declaración de métodos que puede que ni siquiera tengan lógica dentro de la clase:

  • Si que tiene lógica calcular la velocidad en aire y tierra para un pato.
  • No tiene lógica calcular la velocidad en el aire para una vaca (las vacas no vuelan, por ahora...).

El principio corregido

En este caso no debemos ser tan específicos a la hora de incluir métodos dentro de la interfaz.

Lo más correcto sería crear un método de cálculo de velocidad más genérico y nos llevamos a las clases que implementen esta interfaz el factor que es diferenciador. En este caso podemos imaginar que hay un factor aire y otro factor tierra.

El principio corregido según el ejemplo anterior queda así:

interface Animal{

    //método que calcula la velocidad
    public function calculateSpeed(){}
    
}

//clase vaca
class Cow implements Animal{
    
    const EARTH_FACTOR= xxx;

    //método que calcula la velocidad en tierra
    public function calculateSpeed(){
    
        //funcionalidad de cálculo de velocidad en tierra y que utiliza el factor EARTH_FACTOR
    
    }

}

//clase pato
class Duck implements Animal{
    
    const AIR_FACTOR= xxx;

    //método que calcula la velocidad en tierra
    public function calculateSpeed(){
    
        //funcionalidad de cálculo de velocidad en tierra y que utiliza el factor AIR_FACTOR
    
    }


}

Es bastante lógico pensar que este código será mucho más sostenible en el tiempo, además de que no nos encontraremos con métodos que no tienen ninguna funcionalidad dentro de las clases.

Aquí puede parecer bastante obvio que no se debe implementar este método (las vacas no vuelan), pero a veces no está tan claro y un buen punto de referencia es tener la sensación de que un método no encaja dentro de una clase.

Por último me gustaría daros un consejo en referencia a la implementación de interfaces, ya que estas no deben de ser creadas de comienzo (al menos no hasta que te acostumbres a implementarlas) sino que es mejor comenzar creando las implementaciones primero y cuando tengas la sensación de que repites métodos, llévate los mismos a una interfaz.

Poco a poco irás interiorizando lo útiles que son.

Recuerda que tienes el código visto en este artículo en GItHub: Manual de buenas prácticas de programación.

¡Nos vemos en el siguiente fragmento de conocimiento, equipo!


Escrito por Juan José Ramos

Soy programador web, artesano del código y estudiante incansable. Aunque mi carrera siempre ha ido ligada al backend , últimamente me estoy dejando llevar por las lindezas del front.
No hay nada que me guste más que indentar y pulsar [ENTER] ;).
comments powered by Disqus