Principios SOLID


Principios Solid



SOLID es el acrónimo en el mundo de desarrollo de software, no sólo se limita a java sino a todos los lenguajes, que Michael Feathers creó, basado el paper “Design Principles and Design Patterns” sobre los principios de la programación orientada a objetos que Robert C. Martin(uncle bob) había recopilado en el año 2000.

Cada letra en inglés la palabra SOLID representa:


Recordar:
Cohesión es el grado en que el contenido de un módulo está relacionado entre sí.
Acoplamiento es el grado de interdependencia que tienen dos módulos de software entre sí.


SRP: Principio de responsabilidad única:



A class should have only one reason to change.

Robert C. Martin

Traducido al español sería “una clase debería tener una, y solo una, razón para cambiar”. Esto es precisamente, “razón para cambiar”, lo que Robert Martin identifica como “responsabilidad”. El propio Bob te lo resume asi nomás: “Gather together the things that change for the same reasons. Separate those things that change for different reasons”, es decir: “Reunir las cosas que cambian por las mismas razones. Separa aquellas que cambian por razones diferentes”.



OCP: Principio de abierto/cerrado:



Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.

Bertrand Meyer

El segundo principio de SOLID lo formuló Bertrand Meyer en 1988 en su libro “Object Oriented Software Construction” y dice: “Deberías ser capaz de extender el comportamiento de una clase, sin modificarla”. En otras palabras: las clases que usas deberían estar abiertas para poder extenderse y cerradas para modificarse.

Imaginemos un pintor que pinta figuras, como el siguiente ejemplo:

MAL

 class Figura {}
 
 class Cuadrado extends Figura {}
 
 class Circulo extends Figura {}
 class Pintor {
  void pinta (Collection<Figura> figuras) {
   for (Figura figura: figuras) {
    if (figura instanceof Cuadrado) {
     pinta((Cuadrado) figura);
    } else if (figura instanceof Circulo) {
     pinta((Circulo) figura);
    }
   }
  }

  void pinta (Cuadrado cuadrado) {
   // ...
  }

  void pinta (Circulo circulo) {
   // ...
  }
 }

El problema con el ejemplo anterior es que cuando necesitemos agregar nuevas figuras, el pintor debe modificarse violando el principio de abierto/cerrado. Una solución a este problema que permita extender pero se cierre a modificaciones es el siguiente:

OK

 interface Figura {
  void pinta();
 }

 class Cuadrado implements Figura {
  @Override
  public void pinta() {
   // ...
  }
 }
 class Circulo implements Figura {
  @Override
  public void pinta() {
   // ...
  }
 }
 class Pintor {
  void pinta (Collection<Figura> figuras) {
   for (Figura figura: figuras) {
    figura.pinta();
   }
  }
 }



LSP: Principio de substitución de Liskov:


T obj = new T();
 T obj = new S();

Subtype Requirement: Let φ(x) be a property provable about objects x of type T. Then φ(y) should be true for objects y of type S where S is a subtype of T.

Barbara Liskov

En español y dice que “Sea φ (x) una propiedad comprobable sobre objetos x de tipo T, Entonces φ (y) debería ser verdadero para los objetos Y de tipo S donde S es un subtipo de T”.
Dicho de otra manera: "las clases derivadas deben poder sustituirse por sus clases base".
Esto significa que los objetos deben poder ser reemplazados por instancias de sus subtipos sin alterar el correcto funcionamiento del sistema o lo que es lo mismo: si en un programa utilizamos cierta clase, deberíamos poder usar cualquiera de sus subclases sin interferir en la funcionalidad del programa.
Según el propio Robert Martin incumplir el principio de substitución de Liskov implica violar a su vez también el principio de Abierto-Cerrado.

Dado los siguientes arreglos de objetos:

 Integer[] array;
 Object[] arrayObjects;

¿Hay alguna relación de subtipo entre ellos?

Integer[] array = { 1,2,3 };
 Object[] arrayObjects = array;

Se puede decir que Integer[] es sub-tipo de Object[] ¿Pero es seguro?

 Integer[] array = { 1,2,3 };
 Object[] arrayObjects = array;
 arrayObjects[0] = "Hola";

Si intentamos ejeutar el siguiente código obtendremos la siguiente salida:

Exception in thread "main" java.lang.ArrayStoreException:
java.lang.String
at proves.Main.main(Main.java:12)

En algún momento de la historia existía solo java.util.Date... y llegó java.sql.Timestamp a hacerle compañía que es un Date + nanosegundos, su definición es:

public class Timestamp extends java.util.Date

¿Problemas? No...ninguno, ¿porqué preguntas?
Veamos lo siguiente:

Date date = new Date();
 Timestamp ts = new Timestamp(date.getTime());
 System.out.println(date.equals(ts));
 System.out.println(ts.equals(date));

Recordemos el javadoc para el metodo equals de object: Indicates whether some other object is "equal to" this one. The equals method implements an equivalence relation on non-null object references:

  • It is reflexive: for any non-null reference value x, x.equals(x) should return true.
  • It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • For any non-null reference value x, x.equals(null) should return false.

Según el cual, el método implica debe ser simétrico: si x e y son iguales si y solo si y y x también lo son.

Pero...

Date date = new Date();
 Timestamp ts = new Timestamp(date.getTime());
 System.out.println(date.equals(ts));
 System.out.println(ts.equals(date));

Ejecutar el siguiente código resulta en un true seguido de un false




ISP: Principio de segregación:



Clients should not be forced to depend on methods they do not use.

Robert C. Martin

El cuarto principio definido por el propio Robert Martin dice que "Los clientes no deben ser forzados a depender de métodos que no usen."

En otras palabras es preferible contar con muchas interfaces que definan pocos métodos que tener una interface maestra que haga todo.

Dada la siguiente clase encargada de la comunicación, que decribe 2 métodos para enviar los mismo a través de un String o un Object:

public class Comunicador {
  public void enviaMensaje (String mensaje) {...}
  public void enviaMensaje (Object object) {...}
 }

Se nos pide agregar una encriptación para la misma, por lo tanto agregamos un método mas que se corresponde un nuevo Enum:

enum Encriptacion {
  NINGUNA, ENCRIPTACION_DEBIL, ENCRIPTACION_FUERTE
 }

 public class Comunicador {
  public void setEncriptacion (Encriptacion restriccion) {}
  public void enviaMensaje (String mensaje) {}
  public void enviaMensaje (Object object) {}
 }

Decidimos abstraer la clase a una interface para que distintos clientes puedan hacer uso de ella:

public interface Comunicador {
  static enum Encriptacion {
   NINGUNA, ENCRIPTACION_DEBIL, ENCRIPTACION_FUERTE
  }
  public void setEncriptacion (Encriptacion restriccion);
  public void enviaMensaje (String mensaje);
  public void enviaMensaje (Object object);
 }

Aun que estaríamos forzando a posibles clientes de implementar un nivel de encriptación que nunca necesiten:

public class ComunicadorImpl implements Comunicador {
  ...
 }

La solución es segregar en una interface el envio de los mensajes y en otra la encriptación:

public interface Comunicador {
  public void enviaMensaje (String mensaje);
  public void enviaMensaje (Object object);
 }
public interface Encriptable {
  static enum Encriptacion {
   NINGUNA, ENCRIPTACION_DEBIL, ENCRIPTACION_FUERTE
  }
  public void setEncriptacion (Encriptacion restriccion);
 }
public class ComunicadorImpl implements Comunicador, Encriptable {
  public void setEncriptacion (Encriptacion restriccion) {}
  public void enviaMensaje (String mensaje) {}
  public void enviaMensaje (Object object) {}
 }



DIP: Principio de inversión de dependencia:



A. High-level modules should not depend on lowlevel modules. Both should depend on abstractions.

B. Abstractions should not depend upon details. Details should depend upon abstractions.

Robert C. Martin


Depende de abstracciones, no de clases concretas es el último principio de la lista.

Así, Robert Martin recomendaría en español:

  • A - Los módulos de alto nivel no deberían depender de módulos de bajo nivel. Ambos deberían depender de abstracciones.
  • B - Las abstracciones no deberían depender de los detalles. Los detalles deberían depender de las abstracciones.