Aquí presentamos teoria de este principio. En cada rama encontrarás el codigo de cada principio que servirá como ejemplo y en este README encontrarás teoria.
El orden de SOLID no importa, los 5 tienen igual importancia. Fue presentado por Robert C Martin. Son indepenendientes de cualquier lenguaje. Aplicando estos principios obtenemos un software que será más facil de mantener y modificar con el tiempo. De estos 5 principios, 2 no pertenecen a Robert C Martin, El principio de Liskov fue desarrollado y demostrado por Barbara Liskov y El principio de abierto/cerrado el cual fue creado por Bertrand Meyer.
Razones de aplicar SOLID :
- Calidad de software
- Software de mayor eficaz
- Codigo reutilizable y mantenible en el tiempo
- Codigo escalable, es decir, con mayor facilidad de añadir funcionalidades nuevas
SOLID es indispensable para crear aplicaciones robustas y de bajo acoplamiento.
Es el nivel de interdependencaia entre componentes de software, su medida nos dá cuanto dependen uno de otro. Por componente entendemos a clases, metodos, funciones, etc.
Si dos componentes de software son independientes decimos que están desacoplados. El acoplamiento está relacionada siempre a la cohesión, decimos que si existe bajo acoplamiento tendremos alta cohesión y viceversa. Buscamos un bajo acoplamiento (Señal de buen diseño de software), mejorando así la mantenibilidad, reutilización y minimiza el reemplazo de componentes de software.
La cohesion es el grado entre los elementos de un modulo pertenecen juntos. Antes en acoplamiento hablamos de dos componentes distintos, ahora nos referimos a los elementos de un mismo componente de software. Buscamos una alta cohesión entre los elementos de mi componente, es decir una alta relacion entre ellos, la baja cohesion nos dá un sistema dificil de mantener, testear y reutilizar.
Una clase debe tener una y solo una razón para cambiar, lo que significa es que una clase debe tener una sola responsabilidad.
No cumple con SRP porque la clase player tiene dentro una gran cantidad responsabilidades que nada tiene que ver con la clase en si.
Por ejemplo la clase player tiene metodos de save Player, delete Player, etc. Supongamos que estos metodos se conectan una base de datos NOSQL y por alguna razon ahora nos piden que cambiemos la base de datos a SQL, esto nos obliga a nosotros tener que cambiar todos estos metodos, haciendo muy dificil las modificaciones. Esto sin duda es más facil si decidimos asignar la responsabilidad solo a una clase que se encargue de la persistencia de datos de la clase Player.
Estos metodos no tienen ningun tipo de cohesión, por un lado tratamos el acceso a la BD e datos y por otro lado la logica de negocio de la estadistica del jugador. Si bien todos estos metodos estan relacionados al objeto Player, entre estos elementos no existe ningun tipo de relación.
Como vemos, rompe la definicion de SRP, que dice "Una clase debe tener una y solo una razón para cambiar" y como vemos si queremos podemos cambiar estos dos componentes (La logica de negocio y los metodos de persistencia de datos) siendo estas dos, dos razónes y no una sola para cambiar la clase Player.
Lo que podemos hacer es crear más clases, por ejemplo clase de presentacion del Player (UI), clase de presistencia de datos (Repository), clase de logica (Service) y darle a esas clases las responsabilidades que no corresponde en la clase Player.
Las clases que utilicemos deben estar abiertas o extendidas para ampliar su comportamiento y cerradas a la hora de modificarse. Cerrado a la modificacion quiere decir que las nuevas funcionalidades que añadiremos no debe modificar el codigo que ya existe y al mismo tiempo abierto a la extension, es decir, abierto a ampliar su comportamiento y funcionalidades.
Este principio facilita a la hora de añadir nuevas funcionalidades en nuestra aplicación, seguir estos principios nos ahorra una gran cantidad de bugs. Este principio pretende que cada vez que añadas una funcionalidad, no modifiques el codigo que ya existe y funciona porque podrías perjudicar al funcionamiento que tenia el software.
Abstracciones (Clases abstractas) e interfaces
En nuestro caso tenemos un DiscountManager que es una clase que implementa o "maneja" los distintos descuentos, esta clase debe de ser intocable en cuanto al codigo (Cerrado) pero abiertoa permitir añadir nuevas funcionalidades en este caso añadir más cupones de descuento. ¿Como podemos hacer esto? A partir de interfaces, donde definimos las funcionalidades que DiscountManager.
Lo que haremos será crear una abstraccion, esto puede ser a partir de clases abstractas o a partir de interfaces, luego a cada cupon de descuento extenderá esa interfaz e implementará su propio descuento. DiscountManager lo unico que tendrá que tener es un solo metodo, donde este tendrá como parametro el precio del producto y la clase que extienda la interfaz Discount.
Las clases derivadas deben ser subtituibles por sus clases bases. Este principio nos dicen que si una clase hereda de otra, podemos (Y debemos) utilizar cualquiera de las clases hijas en representación de la clase padre, por ejemplo si una clase Ser humano hereda de la clase Ser vivo, Ser humano puede reemplazar a Ser vivo en cualquier metodo que se requiera como parametro a Ser vivo.
Nunca debemos alterar la clase padre al extenderla a una clase hija.
Si ejecutamos este codigo
Nos genera un error, obviamente porque hay canchas de tenis en donde no se debe de cortar el cesped sin embargo esto no deberia perjudicar a otros objetos que si pueden, es decir, estas clases que generan la excepcion nisiquiera deberian de heredar este metodo de "cut grass", hay una mala implementación la herencia, un mal diseño de la clase padre.
Observemos que la clase padre Court implementa estos metodos y atributos:
Las clases hijas :
La solución es eliminar el atributo grass y metodo cutgrass de la clase padre. Crear interfaces para cada tipo de Court donde en cada interfaz se firme con el metodo correspondiente a la cancha (Cortar, limpiar, etc), entonces cada cancha hereda de la clase padre los atributos que son comunes a cada cancha y a su vez implementen la interfaz que le corresponde.
Como vemos en esa imagen, ya no deja poner en una lista a una clase que no comprende la impleementación comun entre las otras clases hijas.
Basicamente nos dice que ninguna clase debe de implementar metodos que no usa. Es mejor una interfaz especifica que una interfaz de proposito general. Este principio ofrece dos ventajas, sistemas desacoplados y codigo sencillo de rafactorizar.
¿Como detectamos el incumplimiento de este principio?
- Generalmente cuando nos encontramos con interfaces con un gran numero de definiciones de metodos, es mas que probable que la clase que lo implementa no utilice todos sus metodos.
- Otra forma es encontrarnos con metodos poco cohesivos en la interfaz, es decir, metodos que tienen poca relación, por ejemplo un metodo se encarga de mapear un objeto y el otro es una logica de negocio, son dos cosas distintas que es mejor separarlas.
- Otra es encontrar metodos vacios dentro de las clases que implementan estas interfaces, es decir, estos metodos no son utilizados.
Payment es perfectamente lo que se denomina "Interfaz de proposito general" es una muy mala practica, porque estamos acumulando firma de metodos que otras clases jamás la implementarán. Por ejemplo BankTransfer jamás implementará los pagos por tarjeta o por efectivo.
La solución es sencillamente tener interfaces especificas, en lugar de interfaces de proposito general. Sin embargo, podrias tener interfaces de proposito general siempre y cuando utilices, en todas las clases que la implementen, todos los metodos firmados en la interfaz.
Por ejemplo Payment es una interfaz que contiene la firma de un metodo que es utilizada por todas las clases, por lo que podemos extender desde las interfaces especificas hacia la interfaz general que es Payment, entonces cada clase que implementa las interfaces especificas deberán implementar el metodo que es comun por todas las clases.
- Las clases de alto nivel no deberian depender de las clases de bajo nivel
- Las abstracciones no deberian depender de los detalles, los detalles si deben depender de las abstracciones.
Lo que buscamos es que el nucleo del codigo, o la importancia del mismo no este en la implementacion.
Lo que tenemos es un fuerte acoplamiento entre UserService y UserRepository, donde UserService (Clase de alto nivel) depende de UserRepository(Clase de bajo nivel), esto como dijimos debemos de evitarlo.
Esto implica, que imaginemos que ahora Repository cambia la forma de implementarse, por ejemplo ahora requiere de un parametro para que funcione, esto ocaciona que estemos cambiando la clase de alto nivel (Service) para que la implementación funcione.
Precisamente este acoplamiento se soluciona con abstracciones, en lugar de que la clase de alto nivel interactue directamente con la clase de bajo nivel añadimos un intermediario, una interfaz para que el servicio interactue desde ahi con la/s clase/s de bajo nivel.
Creamos una clase (Factory) que es la encargada de crear por nosotros la clase de bajo nivel, dejando que la clase de alto nivel se olvide de ello. La clase factory puede devolver la clase de bajo nivel especifica que requiere la clase de alto nivel, siempre que esta clase de bajo nivel especifica implemente la clase de bajo nivel. En nuestro caso, LocalUserRepository (Clase de bajo nivel especifica) implementa UserRepository (Clase de bajo nivel).
Entonces La clase de alto nivel colabora con la clase de bajo nivel y utiliza el factory, donde el factory se encarga de implementar lo que necesite para devolverle un UserRepository que esta clase de alto nivel requiere.
Hoy por hoy tenemos frameworks que nos ayudan a aplicar esta inversion de dependencias a partir de lo que se conoce como Inversion de control, donde en lugar nosotros hacer todo este trabajo, el framework se encarga de tener control e impementar las clases que necesitemos.
Clases de bajo nivel
Clase de alto nivel, colaborando con factory y factory le devuelve la clase de bajo nivel que necesita



















