Principios SOLID en programación orientada a objetos con Java

Principios SOLID en programación orientada a objetos con Java

Crear código de alta calidad es, sin duda, la misión de quien desarrolle y se preocupe por su aplicación. Hay muchas mejores prácticas que pueden ayudar con esto. Explicaré en este artículo los principios SOLID y cómo describen algunas formas simples para que mejores la forma en la que te desenvuelves en el lenguaje de Programación Orientada a Objetos (POO). Asimismo, mostraré cómo aplicar estos principios en la programación orientada a objetos con Java.

SOLID se refiere a cinco principios de diseño OOP identificados por Robert C. Martin (conocido en la comunidad como el tío Bob) alrededor de la década de 2000. Pero fue Michael Feather quien creó el acrónimo tras notar que estos principios podrían encajar en un solo término.

SOLID surge de las siglas iniciales de lo siguiente:

  • Single Responsibility Principle (Principio de Responsabilidad Única);
  • Open-Close Principle (Principio Abierto Cerrado);
  • Liskov Substitution Principle (Principio de Sustitución de Liskov);
  • Interface Segregation Principle (Principio de Segregación de Interfaz);
  • Dependency Inversion Principle (Principio de Inversión de Dependencia).

En general, estos principios ayudan a los desarrolladores a escribir un código más legible y mantenible, evitando el acoplamiento de código donde las clases y los objetos se vuelven dependientes entre sí. También aseguran que el software sea modular, fácil de entender, depurable y factorizable.

Entonces, primero comienzo mostrando para qué sirve Java y luego explicaré, con ejemplos,  cada uno de estos principios y por qué debemos considerarlos importantes al desarrollar una aplicación en Java o en otro lenguaje OOP.

¿Para qué sirve Java?

Java es uno de los lenguajes de programación más populares para construir aplicaciones y plataformas web. Al estar diseñado para ser flexible, Java permite a los desarrolladores escribir código ejecutable en cualquier máquina, independientemente de la arquitectura o de la plataforma.

Además, también se utiliza en tecnologías del lado del servidor como Apache, JBoss, GlassFish, etc., mientras que muchos recursos de Java son valiosos para el análisis de Big Data y para aplicaciones de informática científica.

Java es un lenguaje OOP, lo que significa que todo lo que contiene es un objeto. Es un lenguaje multiproceso con gestión automática de memoria,  es decir, las aplicaciones Java pueden almacenar una gran cantidad de datos en tiempo de ejecución, las cuales pueden utilizarse para verificar y resolver accesos a objetos en tiempo real.

Todo esto significa que el lenguaje sigue siendo relevante hoy. Sin embargo, vale la pena recordar que para implementar los principios SOLID en Java, debe comprender los conceptos básicos de programación orientada a objetos, como la herencia, el polimorfismo, la abstracción y la encapsulación.

S - Principio de Responsabilidad Única

Como era de esperarse, este principio establece que una clase debe tener una sola responsabilidad. En otras palabras, cada clase o estructura similar en el código de su aplicación solo debe hacer un trabajo.

Esto significa que una clase debe tener una y solo una razón para cambiar, al tiempo que se puede aplicar a funciones, componentes, entidades, etc.

Es muy común violar este principio creando clases que hacen de todo, conocidas como “God Class”. En algún momento, se vuelve difícil realizar un cambio en esta clase sin comprometer otras partes del sistema.

Veamos un ejemplo de código que no utiliza el principio de responsabilidad única en Java:


Al principio parece que no hay problema, pero aquí tenemos mezcladas la lógica de autenticación de usuario y la lógica de validación de roles. Supongamos que hay un problema con el método “temCargo”. No solo los usuarios no podrán iniciar sesión en el sistema, sino que todas las funciones relacionadas con este método dejarán de funcionar, al igual que el método “usuarioValido”.

Resolviendo el problema según el principio de Responsabilidad Única:


Ten en cuenta que cada responsabilidad ahora se ha separado en una clase, lo que facilita cambiarlas y probarlas.

O – Principio Abierto-Cerrado

El Principio Abierto-Cerrado establece que las clases u objetos deben estar abiertos para la extensión, pero cerrados para la modificación. Está bien, pero ¿qué significa esto en la práctica?

"Abierto para la extensión" dice que debes diseñar tus clases para que se puedan agregar nuevas funciones a medida que se generan nuevos requisitos. Por su parte, "Cerrado por modificación" significa que una vez que se ha desarrollado una clase, nunca debe modificarse, excepto para corregir errores y otros problemas en el código.

En resumen, cuando es necesario agregar nuevos comportamientos y funciones al software, debemos extender en lugar de cambiar el código original.

Ejemplo de una clase que representa contratos de empleados:


Imagina que este código se elaboró ​​antes de la reforma de la legislación laboral y se generalizaron nuevas formas de contratación. Si la empresa adoptara nuevas formas de contratación, sería necesario modificar la clase “Nómina” y en consecuencia se rompería el principio Abierto-Cerrado.

Una posible solución sería:


Como resultado, la clase "Nómina" ya no necesita saber a cuáles métodos llamar para calcular el pago. Siempre que la interfaz "Compensado" esté en su lugar, puedes calcular correctamente la compensación de cualquier nuevo tipo de empleado creado en el futuro sin tener que cambiar tu código fuente.

L - Principio de sustitución de Liskov

Este principio toma su nombre de Barbara Liskov, quien en 1988 formuló la siguiente definición formal:

“Si para cada objeto O1 de tipo S hay un objeto O2 de tipo T tal que, para todos los programas P definidos en términos de T, el comportamiento de P no cambia cuando se reemplaza O1 por O2, entonces S es un subtipo de T”.

Es decir, las clases derivadas pueden ser sustitutos de sus clases base o incluso todas y cada una de las clases derivadas pueden usarse como si fueran la clase base.

Ejemplo:


¿Por qué ese principio es necesario?

Al cumplir con este principio, se asegura de que la clase derivada se use de forma transparente donde se ve la clase base. Por lo tanto, todo el código que dependa de la clase base podrá usar cualquiera de los derivados en tiempo de ejecución, incluso sin saber que existen.

Entonces, con este principio en mente, presta atención a tu jerarquía de clases y haz un buen uso del polimorfismo.

I - Principio de Segregación de Interfaz

De acuerdo con este principio, nunca se debe forzar a una clase a implementar interfaces y métodos que no usará. En términos generales, este principio establece que es mejor construir interfaces pequeñas y específicas en lugar de interfaces genéricas.

Ejemplo de cómo se manejan algunos pájaros en un juego de Java:


Es evidente cómo asignamos comportamientos genéricos a todas las demás clases. La Clase Penguin, por ejemplo, terminó siendo obligada a tener el método “setAltitude”, una situación que no debería ocurrir porque los pingüinos no vuelan.

Para ello, te recomiendo la siguiente interfaz más específica, que te permitirá aislar correctamente los comportamientos de las aves:

D - Principio de Inversión de dependencia

Este principio establece que, primero, los módulos de alto nivel no deben depender de módulos de bajo nivel,  sino de abstracciones; y segundo, que las abstracciones no deben depender de los detalles; los detalles deben depender de abstracciones.

La idea es que aislamos nuestra clase detrás de un límite formado por las abstracciones que dependen de ella. Si los detalles detrás de estas abstracciones cambian, nuestra clase seguirá estando a salvo. Esto ayuda a mantener el acoplamiento suelto y hace que nuestro diseño sea más fácil de cambiar al permitirnos probar las cosas de forma aislada.

Veamos un ejemplo en Java donde esto no sucede, donde la clase Car depende de la clase Engine:


El código funcionará, pero ¿y si queremos agregar otro tipo de motor, digamos un motor diesel? Esto requerirá refactorizar la clase Car. Sin embargo, podemos resolver esto agregando una capa de abstracción a través de una interfaz:


Ahora podemos conectar cualquier tipo de motor que implemente la interfaz del motor a la clase de automóvil:


Conclusión

El uso de estos principios sigue siendo relevante en el contexto actual. Con SOLID aseguras que tu programa sea modular, comprensible, depurable y factorizable. Además, facilita el aprendizaje y la asimilación de conceptos e ideas a través de estos ejemplos de lenguajes basados ​​en Java, que pueden ser utilizados para otros lenguajes OOP. Por lo tanto, seguir SOLID ayuda a los desarrolladores a ahorrar tiempo y esfuerzo en el desarrollo y mantenimiento de sus aplicaciones.

⚠️
Las opiniones y comentarios emitidos en este artículo son propiedad única de su autor, y no necesariamente representan el punto de vista de Revelo.

Revelo Content Network da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.