Principios SOLID (Single Responsibility & Open/Closed) aplicados al desarrollo de software con Java

En este tutorial discutiremos los dos primeros principios SOLID del diseño orientado a objetos ejemplificando cada principio utilizando código Java.

Lo primero que debemos entender son las razones de porque se crearon estos principios y porque deberíamos considerarlos al momento de diseñar software. Hagamos historia:

Los principios SOLID fueron presentados por Robert C. Martin en su artículo del año 2000 “Principios de diseño y patrones de diseño“.  Estos conceptos fueron desarrollados más tarde por Michael Feathers, quien nos presentó el acrónimo SOLID. Y en los últimos 20 años, estos 5 principios han revolucionado el mundo de la programación orientada a objetos, cambiando la forma en que escribimos software.

¿qué es SOLID y cómo nos ayuda a escribir mejor código?

En pocas palabras, los principios de diseño de Martin y Feathers nos alientan a crear software más fácil de mantener, comprensible y flexible. En consecuencia, a medida que nuestras aplicaciones crecen en tamaño, podemos reducir su complejidad y ahorrarnos muchos dolores de cabeza en el futuro. Los siguientes 5 conceptos conforman los principios SOLID:

  1. Single Responsibility
  2. Open/Closed
  3. Liskov Substitution
  4. Interface Segregation
  5. Dependency Inversion
PrincipioDescripción
Single ResponsibilityUna clase debería tener solo una responsabilidad y solo una razón para cambiar
Open/ClosedLos componentes deberán estar abiertos para poder extender su funcionalidad pero cerrados para modificarlos
Liskov SubstitutionLas clases derivadas deberían ser completamente sustituibles por sus tipos base. Si la clase A es un subtipo de la clase B, deberíamos poder reemplazar B con A sin interrumpir el comportamiento de nuestro programa
Interface SegregationLos clientes no deberían ser forzados a implementar métodos innecesarios que no usarán. Simplemente significa que las interfaces más grandes deben dividirse en otras más pequeñas.
Dependency InversionEl principio de inversión de dependencia se refiere al desacoplamiento de módulos de software. De esta forma, en lugar de que los módulos de alto nivel dependan de los módulos de bajo nivel, ambos dependerán de abstracciones.
Principios SOLID

Principio de Responsabilidad única (Single Responsibility)

Una clase solo debe tener una sola responsabilidad, además, debe tener solo una razón para cambiar. Veámoslo con un ejemplo.

Supongamos que estamos diseñando un software para conectarnos a la tabla de una base de datos, leer algunos datos de esa tabla y escribir los resultados de dicha lectura en un archivo JSON. Las 3 funcionalidades que tenemos son:

  1. Conectarnos a la base de datos
  2. Leer los datos de la tabla
  3. Escribir un archivo json con los datos

La clase que escribamos tendría varias razones para cambiar:

  • Por ejemplo se puede cambiar la base de datos (inicialmente leíamos los datos desde Oracle y ahora necesitamos leerlos desde MySQL)
  • Cambia el archivo json de salida porque cambiaron los datos
Si este fuera el diseño de nuestra clase estaríamos rompiendo el principio de responsabilidad única

A la clase Client le estamos dando responsabilidades que no le competen. Una forma de corregir esto sería por ejemplo llevar la lógica de acceso a datos a una clase llamada ClientDAO (aplicando el patrón DAO) para encapsular todo el acceso al origen de datos, la conexión a la base de datos separarla con algún ORM como Hibernate u otro y/o crear una “fábrica de DAO’s”, la escritura del archivo por ejemplo en una clase llamada FileWriterService cuya única responsabilidad sea escribir los datos recibidos en un archivo json.

Clase Client solo con sus getters y setters
Definiciones o contratos del DAO del cliente
Implementación del contrato
FileWriterService class

Veamos otro ejemplo de la vida real donde aplicar el principio Single Responsibility

Principio Abierto/Cerrado (Open/Closed)

“Abierto a la extensión” significa que debemos diseñar clases para que se puedan agregar nuevas funcionalidades a medida que se van creando nuevos requerimientos. “Cerrado para modificación” significa que una vez que desarrolló una clase, nunca debe modificarse excepto para corregir errores. Parece complicado, pero no lo es tanto y la herencia/composición resuelve el problema, veámoslo en acción.

En el ejemplo anterior de forma intencional se agregó el principio abierto cerrado con las clases ClientDAO y ClientDAOImpl. En la interface ClientDAO agregamos los métodos que son obligatorios para controlar el acceso a datos del Cliente, además si ahora la empresa nos solicita que el software debe además poder insertar y modificar clientes podemos extender fácilmente la funcionalidad de la siguiente forma:

Extendemos la funcionalidad en nuestro DAO
Agregamos la implementación a ClientDAOImpl

Veamos otro ejemplo con una violación del principio OCP

Clase que representa una operación de Suma en la calculadora
Clase que representa una operación de Resta en la calculadora
Clase que contiene el método para calcular según el tipo de operación

¿Qué pasaría si deseamos agregar la lógica para calcular multiplicaciones y divisiones?. Tendríamos que modificar la clase Calculator y agregar dicha lógica ahí, rompiendo con el principio Closed (cerrado para modificaciones). ¿Cómo lo solucionamos?

Sacando la lógica de ejecución de la operación

calculateOperation será genérico para todas las operaciones
Implementamos el método creado según la lógica de suma
Implementamos el método creado según la lógica de resta
Probamos la implementación del código

Extendamos la funcionalidad a división

Implementamos el método creado según la lógica de división extendiendo desde la interfaz CalculatorOperation

Probamos nuevamente

Resultado final de la adaptación del código al principio Open/Closed

De esta forma nuestro programa queda completamente abierto a nuevas funcionalidades pero cerrado a modificaciones de la implementación particular de cada clase.

En una próxima entrega ejemplificaré los 3 últimos principios: Liskov Substitution, Interface Segregation y Dependency Inversion

Please follow and like us:
Juanjo González

Recent Posts

🚀 Aprende a Crear un Login con Python Flask en Minutos! | Tutorial Completo

En este tutorial te mostraré cómo crear un sistema de login con Python Flask de…

4 meses ago

🚀 Tutorial de Flask con Python: Crea un Mantenedor Totalmente Funcional con SQLAlchemy y MySQL 💻

Aprende a crear un mantenedor completo y funcional en este Tutorial de Flask con Python,…

5 meses ago

Subir imágenes a un servidor con Python Flask en 10 minutos!

¿Quieres aprender a subir imágenes a un servidor con Python Flask de manera fácil y…

5 meses ago

Principios SOLID con Python: ejemplo práctico de Responsabilidad Única

Cuando se crea un proyecto Python mediante programación orientada a objetos (POO), una parte importante…

5 meses ago

Guía para entrevistas técnicas como Ingeniero de Software

Navegando en Twitter sobre temas de programación y tecnología encontré esta guía para entrevistas técnicas…

5 meses ago

¿Qué son y para qué sirven los microservicios?

En el mundo de la programación de software, surgen los microservicios como una innovación clave.…

6 meses ago