Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java

En el artículo anterior habíamos abordado dos de los cinco principios SOLID para el desarrollo de software con Java, esta vez continuaremos con Liskov, Interface Segregation y 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 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

Liskov Substitution

Las 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 con A sin interrumpir el comportamiento de nuestro programa

El principio es muy similar al concepto de diseño por contrato propuesto por Bertrand Meyer

Un método sobreescrito de una subclase necesita aceptar los mismos valores de parámetros de entrada que el método de la superclase.

Reglas del juego

  • Este principio se aplica a las jerarquías de herencia y es solo una extensión del Principio de apertura y cierre.
  • Debemos asegurarnos que las nuevas clases derivadas extiendan las clases base sin cambiar su comportamiento original. Básicamente, las clases derivadas nunca deberían hacer menos que su clase base.
  • Si un subtipo del supertipo hace algo que el cliente del supertipo no espera, esto constituye una violación de LSP.

Veámoslo en acción

Supongamos el siguiente escenario: Supongamos que una librería nos pide que agreguemos un nuevo tipo de funcionalidad de entrega de libros en la app. Entonces, creamos una clase BookDelivery que informa a los clientes sobre la cantidad de ubicaciones donde pueden recoger su pedido de la siguiente forma:

Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
BookDelivery class

Sin embargo, la tienda también vende mapas de carteles de lujo que solo quieren entregar en sus tiendas principales. Entonces, creamos una nueva subclase PosterMapDelivery que amplía la funcionalidad de BookDelivery y anula el método getDeliveryLocations() con su propia funcionalidad:

Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
PosterMapDelivery class

Más tarde, la tienda nos pide que también creemos funcionalidades de entrega para audiolibros. Ahora, ampliamos la clase BookDelivery existente con una subclase AudioBookDelivery. Pero, cuando queremos anular el método getDeliveryLocations(), nos damos cuenta de que los audiolibros no se pueden entregar en ubicaciones físicas.

Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
AudioBookDelivery class

Podríamos cambiar algunas características del método getDeliveryLocations(), sin embargo, eso violaría el principio de sustitución de Liskov. Después de la modificación, no podemos reemplazar la superclase BookDelivery con la subclase AudioBookDelivery sin romper la aplicación.

Mejorando el diseño

Para solucionar el problema vamos a utilizar los siguientes principios de diseño OOP:

  • Coding to the Interface: “Codificación para la interfaz”, “codificación contra la interfaz”, “programación basada en la interfaz”, son solo algunos nombres que se le dan a esta técnica increíblemente útil y escalable para escribir sistemas que dependen en gran medida de los cambios. La codificación de interfaces es una técnica para escribir clases basadas en una interfaz; interfaz que define cuál debe ser el comportamiento del objeto.
  • Encapsulate what varies: es una técnica que nos ayuda a manejar detalles que cambian con frecuencia. El código tiende a enredarse cuando se modifica continuamente debido a nuevas características o requisitos. Al aislar las partes que son propensas a cambiar, limitamos el área de superficie que se verá afectada por un cambio en los requisitos.
  • Composition over inheritance: la herencia por lo general genera objetos que son altamente acoplados y dependientes entre sí, algo que evita la composición.

Introduzcamos una capa adicional que diferencie mejor los tipos de entrega de libros. Las nuevas clases OfflineDelivery y OnlineDelivery dividen la superclase BookDelivery. También moveremos el método getDeliveryLocations() a OfflineDelivery. A continuación, crearemos un nuevo método getSoftwareOptions() para la clase OnlineDelivery (ya que es más adecuado para entregas en línea). Por ejemplo, el siguiente código demuestra el concepto.

Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java

Después de la refactorización, podríamos usar cualquier subclase en lugar de su superclase sin interrumpir la aplicación.

Interface Segregation

Los 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.

En el ejemplo anterior de la entrega de libros podemos aplicar Interface Segregation para solucionar el problema:

Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java

Veamos otro ejemplo: supongamos que estamos escribiendo un software para gestionar los pedidos de McDonalds. Los pedidos u órdenes pueden ser de hamburguesas, papas fritas o un combo (ambas opciones).

Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java

Dado que un cliente puede pedir papas fritas, una hamburguesa o ambos, decidimos poner todos los métodos de pedido en una sola interfaz.

Ahora, para implementar un pedido solo de hamburguesas, nos vemos obligados a lanzar una excepción en el método orderFries():

Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java

De manera similar, para un pedido solo de papas fritas, también tendríamos que generar una excepción en el método orderBurger().

Y esta no es el único problema de este diseño. Las clases BurgerOrderServiceFriesOrderService también tendrán efectos secundarios no deseados siempre que hagamos cambios en nuestra abstracción.

Para solucionar este problema se desagregan las interfaces en componentes más pequeños:

Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java

Cuidado al utilizar ISP

Aplicar el ISP al extremo dará como resultado interfaces de método único, también conocidas como interfaces de rol.

Esta solución resolverá el problema de la violación de ISP. Aún así, puede resultar en una violación de la cohesión en las interfaces, lo que resulta en una base de código dispersa que es difícil de mantener. Por ejemplo, la interfaz Collection en Java tiene muchos métodos como size()isEmpty() que a menudo se usan juntos, por lo que tiene sentido que estén en una sola interfaz.

Hay muchos ejemplos más en la red de la aplicación de este principio, este me pareció interesante también.

Dependency Inversion

El 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.

Supongamos que una librería nos pide que construyamos una nueva función que permita a los clientes colocar sus libros favoritos en una biblioteca.

Para implementar la nueva funcionalidad, creamos una clase Libro de nivel inferior y una clase Biblioteca de nivel superior. La clase Libro permitirá a los usuarios ver reseñas y leer una muestra de cada libro que almacenan en sus bibliotecas. La clase Biblioteca les permitirá agregar un libro a su biblioteca y personalizarla.

Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Libro class
Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Biblioteca class

Todo parece bien, sin embargo la clase Biblioteca de alto nivel depende del Libro que es de bajo nivel, este código viola el principio de inversión de dependencia. En el siguiente diagrama de clases se aprecia mejor el problema:

Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Violación del principio de inversión de dependencia

Si la librería nos pide que permitamos que los clientes agreguen por ejemplo DVD a sus bibliotecas pasaría lo siguiente:

Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
DVD class

Ahora tendríamos que modificar la clase Biblioteca para aceptar los DVD’s, rompiendo con el principio Open/Closed también.

La solución a este problema es crear una capa de abstracción para las clases de nivel inferior (Libro y DVD), para esto creamos la interfaz Producto:

Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Diagrama de clases de la solución
Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Interface Producto
Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Clase Libro
Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Clase DVD
Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Clase Biblioteca

El código anterior también sigue el principio de sustitución de Liskov, ya que el tipo de producto se puede sustituir por sus dos subtipos (libro y DVD) sin romper el programa. Al mismo tiempo, también hemos implementado el Principio de Inversión de Dependencia, ya que en el código refactorizado, las clases de alto nivel tampoco dependen de las clases de bajo nivel.

Relación del principio de inversión de dependencia con inyección de dependencia

El tío Bob Martin introdujo el concepto de inversión de dependencia antes de que Martin Fowler introdujera el término inyección de dependencia. Estos dos conceptos están extremadamente relacionados. La inversión de dependencia está más concentrada en la estructura del código. Además, su objetivo es mantener el código lo menos acoplado posible. Por otro lado, la inyección de dependencia se trata de cómo funciona funcionalmente el código.

En una próxima entrega abordaré aspectos técnicos de inyección de dependencia con algunos frameworks como Spring para java y algunos ejemplos prácticos en .NET. Espero que el artículo haya sido de utilidad.

Visited 10 times, 1 visit(s) today
Please follow and like us:
Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java
Principios SOLID (Liskov, Interface Segregation & Dependency inversion) aplicados al desarrollo de software con Java

Leave a Comment

URL has been copied successfully!
URL has been copied successfully!
RSS
Follow by Email
Facebook
X (Twitter)
Visit Us
Follow Me
Tweet
Youtube
Youtube
Whatsapp
Reddit
Copy link