Ahora veremos el último de los Principios de Diseño Orientado a Objetos, para el cual les recomiendo antes, revisar y entender el Principio de Responsabilidad Única, el de Abierto/Cerrado y el de Segregación de Interfaces.
Dicho esto, ya podemos empezar con el Principio de Inversión de Dependencias, el cual sostiene dos puntos clave, el primero de ellos es:
"Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de abstracciones."
Entendamos los módulos de alto nivel como aquellos elementos de nuestro sistema que están más cerca de nuestro cliente, que bien podría ser una persona u otro sistema, y los de bajo nivel como aquellos que están más cerca al núcleo o centro de nuestro sistema.
El siguiente punto clave es:
"Las abstracciones no deben depender de detalles, son los detalles los que deben depender de abstracciones."
Por ejemplo, cuando vamos a comprar a un supermercado y debemos recibir algún comprobante, independientemente a que sea una boleta o una factura, existe la operación "Emitir Comprobante", y del mismo modo cuando falta poco tiempo para que acabe el servicio mensual de luz o agua, hay una suerte de "Enviar Notificación", que bien puede ser por correo electrónico, un documento físico o una llamada telefónica.
Las abstracciones deben capturar el flujo o comportamiento esencial de nuestro sistema sin caer en especificaciones del detalle de su implementación.
Dejando estos dos puntos claros pasemos al siguiente ejemplo:
Podemos apreciar que hay una serie de operaciones en relación al pago de un carro de compras y se podría decir que no hay problema, pero la verdad es que esta situación esta cometiendo algunas infracciones. Y esto es así porque estamos trabajando con detalles, con implementaciones concretas, además de tener una clase que no solo es responsable de hacer un Checkout, sino también de inicializar los servicios que usaremos en todo el proceso.
Antes de proseguir con esto, debo mencionar que así como existen los principios de diseño orientado a objetos también existen una variedad de patrones de diseño que nos pueden ayudar dependiendo a la situación y en esta ocasión, elegiremos la inyección de dependencias por constructor.
Entonces, para el anterior ejemplo, lo adecuado sería definir las siguientes interfaces según las operaciones que hemos identificado:
Una vez definidas, ya podremos conseguir la siguiente actualización para la clase en cuestión y, como se puede apreciar, en el constructor de la clase se reciben las interfaces que previamente hemos definido.
La pregunta que viene ahora, ya que tenemos varias interfaces que eventualmente deben inicializarse, es: ¿Dónde inicializamos los objetos? Esto se puede realizar a través de un contenedor de inversión de control (IoC Container) o definiendo un constructor por defecto, pero para este ejemplo, las inicializaremos manualmente. Primero asegurémonos de implementar las interfaces:
Y una vez hecho eso, ya podemos continuar de la siguiente forma, definiendo clases concretas según las interfaces y enviando como parámetros de constructor todo lo necesario para hacer nuestro Checkout.
Como pueden ver, resulto un poco más trabajoso entender este principio, pero la verdad es que trae interesantes oportunidades. Por ejemplo, podemos tener diferentes implementaciones de las interfaces y realizar pruebas con mucha facilidad.
Para terminar, os quiero hacer acordar que estos principios de diseño son guías, si sientes que tu proyecto no amerita ser estricto con alguno de ellos, no te hagas problemas, lo importante es que sea eficiente.
No hay comentarios.:
Publicar un comentario