Saltar al contenido

Etiqueta: patterns

Interface Segregation Principle

El penúltimo principio del que nos toca escribir hoy es el de la segregación de interfaces. El corolario de este principio nos indica lo siguiente:

Los clientes no deberían ser forzados a depender de métodos que no vayan a usar.

El problema que se tiene con las grandes interfaces podemos verlo claramente en la clase MembershipProvider que usaba microsoft hace algún tiempo.

MembershipProvider de Microsoft

Hace un tiempo, microsoft sólo soportaba SQL Server como provider, por lo que si se necesitaba usar algún otro provider, era necesario implementar todos los métodos que pueden ver en la imagen.

El resultado de violar este principio, nos lleva a no implementar completamente las interfaces, dado que no es necesario usar todos métodos, esto hace que poco a poco empecemos a adquirir en nuestro código más dependencias, lo cual es muy probable que nos termine ocasionando las siguientes complicaciones:

  • Más acoplamiento.
  • Código más frágil.
  • Más dificultas de probar el código.
  • Más dificultas en los pases a producción.

Básicamente, lo que nos indica es que debemos evitar crear interfaces con demasiados métodos. Esto lo conseguimos separando las responsabilidades de nuestra interfaces. Para poder determinar si en nuestros proyectos estamos violando este principio, es necesario buscar grandes interfaces, una muestra clara la podemos ver si en nuestro código encontramos algún método que implementa una interfaz y una de estas implementaciones tienen un «NotImplementedException».

Respetar Interface Segregation Principle

Si se diera el caso en que tenemos refactorizar un proyecto para que se respete este principio, podemos considerar las siguientes estrategias, sea cual sea el caso en el que nos encontremos.

  • Romper una interfaz grande en pequeñas (siempre y cuando nosotros tengamos control de las interfaces a refactorizar).
  • Si se diera el caso en el que dependemos de una gran interfaz (entiéndase un caso como la implementación del provider indicado lineas arriba), lo que se debe hacer es mediante el patrón adapter segregar las interfaces que necesitemos, y sólo este adapter conocerá todos los métodos de la interfaz que queremos disminuir.

La demo que desarrollare para demostrar lo mencionado en este post, consta de una clase que llamaremos IMachine, que es el simil de una impresora multifuncional.

Interfaz IMachine

Si queremos implementar esta interfaz en un método que sólo imprima, nos veremos en la siguiente situación.

Implementación de IMachine, sólo para imprimir

Con métodos que no necesitamos, por esto se crea una interfaz para cada una de las acciones «IFax», «IPrint» y «IScann». Del mismo modo, si se requiriera que nuestro equipo escanee y también imprima, podríamos crear una interfaz que herede a su ves de estas dos interfaces, del siguiente modo:

Herencia entre interfaces

Como ven, no se esta generando ningún método dentro, dado que el IScan y el IPrint, ya tiene sus implementaciones propias. Así mismo, cuando hacer la implementación de nuestra interfaz IMultiFunction, podemos valernos del patrón decorator para no implementar nuevamente los métodos de imprimir y de scan, sino usar los que ya existen en estas interfaces.

Aplicación del patrón decorator

Espero que con este post se tenga un poco más claro el concepto que nos brinda este principio. El último principio que compone SOLID es el ded la inyección de dependencia, y este lo estaremos revisando probablemente en el siguiente post. Como siempre, dejo el video del desarrollo del post y el link al repositorio en github.

REPO: GITHUB

Listado de POST sobre SOLID anteriores:

Deja un comentario

Liskov Substitution Principle

El tercer principio que compone SOLID es el Liskov Substitution Principle, y este nos plantea

Plantea una guía de cuando debemos usar herencia en POO.

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, 1988

Este principio, podemos entenderlo en su modo más simple de la siguiente forma «Las clases derivadas, deben poder ser sustituidas por sus clases base».

Para ejemplificar este principio, usaremos el típico ejemplo del rectángulo y el cuadrado. Veamos el proyecto que tenemos definido:

Nuestra clase base es el rectángulo:

Clase base rectángulo

Queremos calcular el área de nuestro rectángulo o las clases que lo implementen, como un cuadrado. Para esto, creamos el siguiente método:

Método para hallar el área

Digamos que ahora necesitamos implementar un cuadrado, y con esta finalidad creamos la siguiente clase:

Clase del cuadrado

Como ven, el cuadrado implementa nuestro rectángulo y lo que hacemos es definir en sus propiedades que las propiedades base (las que esta implementado) son iguales en su ancho y altura, que como sabemos corresponden a los lados de un cuadrado. Si probamos obtener el cuadrado de ambas clases, tendremos el siguiente resultado:

Resultado al obtener el área sin probar el principio Liskov

Notese que se esta creando una nueva instancia del Square y por esto obtenemos los resultados correctos. Sin embargo, si buscamos poner en práctica lo definido por el principio liskov, veamos que resultado obtenemos.

Resultado poniendo a prueba el principio Liskov

Lo que sucede es que nuestra clase Rectangle, es la que es inicializada, por lo tanto sólo posee el Width igual a 4, y no el Height. Cuando se busca obtener el área realiza la multiplicación del ancho por la altura y tenemos como resultado 0. Para poder respetar este principio, es necesario que realicemos el siguiente cambio:

Cambios para respetar el principio Liskov

Lo que hacemos es declarar como virtuales las propiedades de nuestra clase base, por lo que cuando se incialice y el compilador determine que es virtual, va a buscar algún override que es justo lo que hacemos en la clase derivada. De este modo, sabe que debe usar la propiedad de Square e iguala los lados a 4 que es lo que definimos en el Width. Si probamos ahora nuestro método, tendremos el siguiente resultado:

Prueba final del cálculo del área

Donde como se ve, ya se respeta el principio que hemos tocado ampliamente en este post. Espero que les ayude a entender un poco más este principio, así mismo comentarles que este es el tercer post de los principios que componen SOLID. Como siempre, dejo el video del desarrollo de esta post y el link al repositorio en github.

REPO: GITHUB

Listado de POST sobre SOLID anteriores:

Deja un comentario

Open Close Principle

Siguiendo con la linea de post que presentan los principios que componen marco de buenas prácticas SOLID. El que vamos a desarrollar en este post es el «Open Close Principle», esto hace referencia a que el código que desarrollamos debería estar abierto a ser extendido y cerrado a las modificaciones.

Sin más, veamos el ejemplo que vamos a desarrollar en este post:

Supongamos que estamos desarrollando un sistema para un almacen, en el cual se manejan productos. Estos productos, poseen características, las cuales pueden ser:

Clase de Productos

En donde los «Enums» mostrados poseen los siguientes elementos:

Clase de Enums

Digamos que por necesidad del negocio, nos vemos obligados a filtrar los productos que vamos almacenando por su color, teniendo esto en mente, creamos una nueva clase que llamaremos «ProductFilter».

De este modo, si queremos filtrar un grupo de colores, por un color en particular deberíamos hacer algo muy similar a esto:

Filtro por color los productos

De este modo, tenemos 3 productos, los cuales se agregan a una lista y esta lista se proporciona al método de filtrado que hemos creado. Si ejecutamos esta aplicación, tenemos el siguiente resultado:

Filtrado por color a los productos
Filtrado Correcto

Como podemos ver, se ejecuta sin problemas. El problema viene cuando requerimos realizar más filtros. Digamos que ahora nos indican que el filtrado también sea por tamaño, o por color y tamaño, terminariamos llenando nuestra clase «ProductFilter» de métodos. Algo muy similar a esto:

Llenado de métodos para diversos tipos de filtros

Como ven, esto rompe claramente el principio de Open Close Principle. Deberíamos de poder extender nuestros filtros, sin afectar la clase que ya se tiene, esto se puede conseguir a través de interfaces. En este caso, para respetar este principio voy a usar el «specification pattern». Veamos como podemos implementarlo y respetar este principio.

Es necesario creas dos interfaces para poder llevar a cabo la implementación de este patrón de diseño, siendo la primera una que nos permitirá definir la especificación, cuando esta va a estar satisfecha y el tipo de elemento que recibe como tipo de entrada y la segunda espara implementar el filtro y como este se lleva a cabo.

Interfaz IFilter
Interfaz ISpecification

Ahora, implementemos el primero de los filtros, este es es el «ColorSpecification»:

ColorSpecification

Como pueden ver, implementamos la interfaz definida en el paso anterior, tenemos una variable que se inicializa en el constructor, que para el filtro es el color y en el método «IsSatisfied» solo se retorna true si el color del producto que nos proporcionan es igual al que se inicializa en el constructor.

Para poder usar este filtro es necesario implementar también la interfaz «IFilter», lo cual realizamos del siguiente modo:

Implementación del IFilter

Del mismo modo que en la implementación anterior, se se le indica el tipo de elemento con el cual se desea filtrar, iteramos sobre la lista de estos elementos y solo solo cuando la especificación haya sido satifecha retornamos, el «yield» lo usamos por la eficiencia en el recorrido de nuestra lista.

Veamos como usamos este filtro en nuestra clase principal y que resultado obtenemos:

Uso del filtro
Resultado del nuevo filtrado

Finalmente, mostrare como se puede realizar el más de un filtro a la vez, para ejemplificar esto, creare un filtro por tamaño, siguiendo el mismo esquema ejemplificado anteriormente. Para poder adicionar más de un filtro es necesario crear una nueva clase pero ahora su tipo también será genérico, del siguiente modo:

AndSpecification para realizar más de un filtro

Inicializamos dos variables en el constructor, ambas del tipo «ISpecification», y sólo se satisface cuando ambas variables lo están. Para consumir este método, debemos de hacerlo del siguiente modo:

Consumiendo más de un filtro a la vez

Se le tienen que indicar los filtros que se quieren realizar y al consumir este filtro en el cual buscamos un producto del color verde y de tamaño pequeño, nuestro resultado es el siguiente:

Resultado de ambos filtros

Espero que les ayude a entender un poco más este principio, así mismo comentarles que este es el segundo post de los principios que componen SOLID. Como siempre, dejo el video del desarrollo de esta post y el link al repositorio en github.

REPO: GITHUB

Listado de POST sobre SOLID anteriores:

Deja un comentario

Single Responsibility Principle

Ya hace un tiempo llevo trabajando en una empresa en el sector financiero, en la cual como toda empresa de su envergadura el stack de tecnologías y aplicaciones que poseen es diverso. Y como comprenderan eso tarde o temprano nos llevaba tener que trabajar con legacy code, y las serie de retos que estos involucran. Más de una vez, he tenido que atajar métodos de más de 1000 líneas de código que tenían como objetivo extornar algún monto, grabar log, etc. Esto se vuelve un dolor se cabeza al querer realizar pruebas unitarias para que sean parte del pipeline que se ejecuta en un ambiente de integración y despliegue continuo.

Este problema pone en evidencia la importancia de desarrollar teniendo siempre en mente la utilización de patrones de diseño. Esto me lleva a tratar en este post el primero de los principios de SOLID, siendo los principios que lo componen no los únicos que existen, pero si un marco de buenas prácticas en el desarrollo de software que nos permite asegurar un nivel de calidad en lo que desarrollamos.

Single Responsability Principle representa la «S» en SOLID, este principio entre otras cosas nos indica que los métodos que desarrollamos deben poseer una sola reponsabilidad y por lo tanto un sólo motivo para cambiar. Cuando nos referimos a responsabilidad, esta puede tomar la forma de las siguiente maneras:

Tipos de responsabilidad
  • Grabar en una base de datos, archivo de texto o cualquier otro medio de persistencia que se desee.
  • Realizar un log del método que se está consumiendo y/o también de los errores que puedan ocurrir.
  • Validar la información ingresada al método.
  • Lógica de negocio.

En lineas generales, debemos buscar que el código que desarrollemos tenga el menor acoplamiento posible y la única manera de conseguirlo es definiendo adecuadamente las responsabilidades. Una buena manera de probar que tanto acoplamiento posee nuestro código es cuantos test unitarios es necesario desarrollar para poder coberturarlo al 100%, es muy usual que si nuestro método desarrollado tiene muchas responsabilidades, el poder realizarle pruebas unitarios se va a volver algo extremadamente completo y tedioso. Para poder probar este patrón de diseño, vamos a reformular código teniendo en cuenta las consideraciones antes mencionadas.

La aplicación que vamos a refactorizar nos permite obtener el cálculo de ratio para la realización de la contratación de una poliza, tenemos un método de más de 100 lineas de código que posee distintas responsabilidades, tal como podemos ver en la siguiente imagen:

Código a refactorizar
  1. Grabación del log cuando inicia la llamada al método.
  2. Lectura de un archivo de texto y conversión a un objeto.
  3. Validación de la data obtenida.
  4. Lógica de negocio.
  5. Persistencia de la información obtenida en un archivo de texto.
  6. Grabación del log cuando finaliza la llamada al método.

Como podemos ver, es necesario reformular el método para que cumpla con los requerimientos descritos por este patrón. Esto lo realizaremos extrayendo ciertas responsabilidades del método principal. El primero nos permitirá realizar el grabado de logs:

Método para grabar logs

Luego, desarrollaremos un método para leer el archivo de texto, el cual debe poder retornar la entidad que le pasemos, por lo tanto es debe ser genérico.

Método para leer de un archivo de texto y retornar una entidad

Además, es necesario extraer el grabado de la persistencia en un método, este también debe aceptar como variables de entradas genericos.

Grabado de la persistencia

Finalmente, quedando el método del siguiente modo:

Método refactorizado

Notese que también he separado en un método distinto las validaciones de las polizas de vida.

Espero que les ayude a entender un poco más este principio, así mismo comentarles que este es el primer post de los principios que componen SOLID. Como siempre, dejo el video del desarrollo de esta post y el link al repositorio en github.

REPO: GITHUB

2 comentarios