Skip to content

willy23martin/software-architecture-poc-solid

Repository files navigation

Prueba de Concepto - Principios de Diseño SOLID

Repositorio de código para la PoC de los Principios SOLID

Introducción

La construcción de buenos sistemas de software depende en gran medida de qué tan “clean” (limpios) están tanto el código (building blocks) como la arquitectura. Por ende, los Principios de Diseño SOLID establecen la manera en que las funciones y los datos deberían agruparse en clases, y cómo estas últimas debería interconectarse [1]. Su objetivo, entonces es el de crear estructuras en módulos que sean capaces de adaptarse al cambio, que sean entendibles fácilmente y que sean la base de componentes reutilizables [1].


Historia

  • 1980Robert C. Martin [1] presenta los principios en un debate de USENET (un sistema predecesor y similar a Facebook).
  • 2000 – Los principios se establecen en un orden diferente.
  • 2004Michael Feathers sugiere ordenar los principios de tal manera que sean llamados SOLID.

Principios de Diseño SOLID

Principio Descripción Antes de Aplicarlo Después de aplicarlo
Single Responsibility Principle (SRP) Cada módulo de software debe tener sólo una razón para cambiar, y esta razón viene definida como un actor (grupo común de stakeholders o usuarios del sistema). En definitiva, el código debería separarse si varios actores dependen de él [1]. 1) Duplicaciones de Código.
2) Merge conflicts.

Ejemplo: la funcionalidad del manejo de la base de datos y de la gestión de las órdenes está acoplada entre las clases porque tienen responsabilidades compartidas. Actores: DBA, usaría la Database para consultar las órdenes; el OrderManager también ejecutará la consulta del consolidado de pago de estas órdenes. Estos se van a ver afectados mutuamente frente a cualquier cambio.
SRP-Before.png
1) DRY principle.
2) Disminuyen merge conflicts.

Ejemplo: los diferentes actores usarán diferentes clases para la ejecución de sus objetivos, con responsabilidades claras y definidas: persister, register y payment calculator.

SRP-After.png
Open-Closed Principle (OCP) Para que un sistema pueda cambiar fácilmente se deben diseñar de tal manera que dicho cambio sea añadiendo más código y no cambiando el existente [1]. 1) Archivos de código fuente con cientos de líneas de código.
2) Diferentes variaciones de un mismo tipo de acción no dependen ni de interfaces ni de abstracciones.

Ejemplo: para la persistencia, se pueden usar diferentes estructuras o bases de datos. No obstante, este esquema que viene de la solución original depende de la implementación en particular de Database sin opción de extensión (o al menos no una limpia).

OCP-Before.png
1) Archivos de código más pequeños y con alta cohesión.
2) Abstracciones e Interfaces soportando extensibilidad.

Ejemplo: a través de IDatabase se logra la extensión a dos opciones de bases de datos, sin afectar el contrato (métodos) de uso desde las otras clases del módulo.

OCP-After.png
Liskov Substitution Principle (LSP) Establece que un sistema de software puede diseñarse de tal manera que sus partes puedan intercambiarse siempre y cuando estas se adhieran a contratos que permitan hacerlo [1]. 1) Módulos interactúan mediante implementaciones concretas y no mediante interfaces.
2) Casting objects o instanceof para definir implementación en concreto.
3) Interacciones guiadas por condicionales if.

Ejemplo: el PaymentCalculator calcula el pago de manera ordinaria. Pero otra manera de hacerlo es a través de puntos de descuento (DiscountPointsPaymentCalculator). No obstante, el módulo implementa una u otra de manera directa sin la posibilidad de hacer una sustitución limpia

LSP-Before.png
1) Interfaces bien definidas para la comunicación inter-modular.
2) Inyección de dependencias para definición de implementaciones concretas.

Ejemplo: por ende, PaymentCalculator se vuelve una abstracción desde la cual extienden los dos métodos: Ordinary y DiscountPoints. De esta manera las otras clases del módulo usarán esta abstracción y podrán reemplazar de manera limpia una implementación con otra.

LSP-After.png
Interface Segregation Principle (ISP) Sugiere no depender de dependencias que no se utilizan [1]. De acuerdo con [2], este principio se alinea con el Lean Movement: crear valor con la mínima cantidad de recursos. 1) Excepciones de tipo UnsupportedOperationException.
2) Comentarios como implementaciones.
3) Implementación vacía de métodos.
4) Módulos que solo usan un subconjunto de los métodos de la interfaz.

Ejemplo: la clase principal utiliza el OrderManagerFacade para conocer el estado de la base de datos. Mientras que el OrderManager solo utiliza la opción de persistencia para almacenar las órdenes. No obstante, persistencia y consulta de datos están acopladas en la misma interfaz IDatabase. Por ende, se necesita segregarla para que ambos mecanismos puedan ser usados de manera separada por cada una. De esa manera se evita que tengan acceso a servicios no deseados (que la clase principal pueda almacenar órdenes).

ISP-Before.png
Interfaces definidas con los métodos justos que necesitan exponer.

Ejemplo: en este caso se segregó en IDatabasePersister, para que el OrderManagerFacade lo use en su OrderRegister. Y en el IDatabaseRetriever, que va a ser usado por la clase principal para averiguar el estado de la base de datos. De esta manera se segregan las responsabilidades y se evitan accesos no deseados.

ISP-After.png
Dependency Inversion Principle (DIP) De acuerdo con [2], los módulos deberían depender de abstracciones y no de implementaciones concretas. 1) Uso de implementaciones concretas en vez de interfaces.
2) Desarrolladores crean instancias de implementaciones concretas cada vez que las necesitan y no le delegan esta creación al framework (Spring IoC Container, por ejemplo) [2].

Ejemplo: cada implementación de bases de datos, que fueron previamente extendidas en el Open-Closed Principle, utiliza una implementación particular de cada DataSource (A y B, correspondientes con una estructura de lista y otra de mapa). Se recomienda depender de abstracciones o interfaces y dejar que las implementaciones concretas las haga el framework mediante inyección de dependencias.

DIP-Before.png
Los módulos hacen uso de las interfaces y dejan que el framework realice la inyección de dependencias acorde con la implementación en particular a utilizar.

Ejemplo: en este caso se crea la interfaz IDataSource para que cualquier tipo de DataSource sea inyectado por el framework (clase principal), en cada Database, de acuerdo con las reglas de negocio.

DIP-After.png

NOTA: un módulo es un conjunto de funciones y estructuras de datos con alta cohesión [2] (aquella que implica el cumplimiento del SRP). Según [2], esto implica que: el nombre del módulo representa su funcionalidad expuesta (mediante interfaces); y que frente a un nuevo cambio se afecten varias de las clases que pertenecen a dicho módulo, debido a la alta cohesión.

Finalmente, la aplicación de estos principios no solo garantiza la calidad y la mantenibilidad del código, sino que también ayuda a promover la agilidad en el ciclo de desarrollo de software.


How to Test it?

Ejecute el método principal (main) de cada clase cuyo nombre se refiera a un principio de diseño SOLID:



Referencias:

  1. Martin C, Robert (2018). Clean Architecture: A Craftsman’s Guide to Software Structure and Design. Pearson Education, pages 57-91, 201-211
  2. Enriquez, René. Salazar, Alberto (2018). Software Architecture with Spring 5.0: Design and architect highly scalable, robust, and high-performance Java applications. Packt, pages 17-28
  3. OpenWebinars - Clean Code

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages