descomposición jerárquica

Nunca debe comenzar a escribir clases para su aplicación de inmediato. Primero hay que diseñarlo. El diseño debe terminar con una arquitectura bien pensada. Y para obtener esta arquitectura, debe descomponer el sistema de manera consistente.

La descomposición debe llevarse a cabo jerárquicamente: primero, el sistema se divide en grandes módulos/subsistemas funcionales que describen su funcionamiento en la forma más general. Luego, los módulos resultantes se analizan con más detalle y se dividen en submódulos u objetos.

Antes de seleccionar objetos, divide el sistema en bloques semánticos básicos, al menos mentalmente. En aplicaciones pequeñas, esto suele ser muy fácil de hacer: un par de niveles de jerarquía son suficientes, ya que el sistema se divide primero en subsistemas/paquetes y los paquetes se dividen en clases.

descomposición jerárquica

Esta idea no es tan trivial como parece. Por ejemplo, ¿cuál es la esencia de un "patrón arquitectónico" tan común como Model-View-Controller (MVC)?

Se trata de separar la presentación de la lógica empresarial . Primero, cualquier aplicación de usuario se divide en dos módulos: uno es responsable de implementar la lógica comercial en sí (Modelo) y el segundo es responsable de interactuar con el usuario (Interfaz de usuario o Vista).

Entonces resulta que los módulos deben interactuar de alguna manera, para ello agregan un Controller, cuya tarea es gestionar la interacción de los módulos. También en la versión móvil (clásica) de MVC, se le agrega el patrón Observer para que View pueda recibir eventos del modelo y cambiar los datos mostrados en tiempo real.

Los módulos típicos de nivel superior, obtenidos como resultado de la primera división del sistema en los componentes más grandes, son precisamente:

  • Lógica de negocios;
  • Interfaz de usuario;
  • Base de datos;
  • Sistema de mensajería;
  • Contenedor de objetos.

La primera división generalmente divide la aplicación completa en 2-7 (máximo 10 partes). Si lo dividimos en más partes, habrá un deseo de agruparlas y nuevamente obtendremos de 2 a 7 módulos de nivel superior.

Descomposición funcional

La división en módulos/subsistemas se realiza mejor en función de las tareas que resuelve el sistema . La tarea principal se divide en sus subtareas constituyentes, que se pueden resolver/realizar de forma autónoma, independientemente unas de otras.

Cada módulo debe encargarse de resolver alguna subtarea y realizar su función correspondiente . Además del propósito funcional, el módulo también se caracteriza por un conjunto de datos necesarios para que pueda realizar su función, a saber:

Módulo = Función + Datos necesarios para ejecutarlo.

Si la descomposición en módulos se realiza correctamente, la interacción con otros módulos (responsables de otras funciones) será mínima. Puede ser, pero su ausencia no debería ser crítica para su módulo.

Un módulo no es una pieza arbitraria de código, sino una unidad de programa separada funcionalmente significativa y completa (subprograma) que proporciona una solución a una determinada tarea e, idealmente, puede funcionar de forma independiente o en otro entorno y reutilizarse. El módulo debe ser una especie de "integridad capaz de una relativa independencia en el comportamiento y el desarrollo". (Cristóbal Alejandro)

Así, la descomposición competente se basa, en primer lugar, en el análisis de las funciones del sistema y los datos necesarios para realizar estas funciones. Las funciones en este caso no son funciones de clase y módulos, porque no son objetos. Si solo tiene un par de clases en un módulo, entonces se excedió.

Conectividad fuerte y débil

Es muy importante no exagerar con la modularización. Si le das a un principiante una aplicación Spring monolítica y le pides que la divida en módulos, sacará cada Spring Bean en un módulo separado y considerará que su trabajo está terminado. Pero no lo es.

El criterio principal para la calidad de la descomposición es cómo los módulos están enfocados en resolver sus tareas y son independientes.

Esto generalmente se formula de la siguiente manera: "Los módulos obtenidos como resultado de la descomposición deben estar máximamente conjugados internamente (alta cohesión interna) y mínimamente interconectados entre sí (bajo acoplamiento externo)".

Cohesión alta, alta cohesión o "cohesión" dentro del módulo, indica que el módulo se centra en resolver un problema limitado y no se dedica a realizar funciones heterogéneas o responsabilidades no relacionadas.

La cohesión caracteriza el grado en que las tareas realizadas por el módulo se relacionan entre sí.

Una consecuencia de High Cohesion es el Principio de responsabilidad única , el primero de los cinco principios SOLID , según el cual cualquier objeto/módulo debe tener una sola responsabilidad y no debe haber más de una razón para cambiarlo.

Acoplamiento bajo , acoplamiento flojo, significa que los módulos en los que se divide el sistema deben ser, si es posible, independientes o acoplados débilmente entre sí. Deben poder interactuar, pero al mismo tiempo saber lo menos posible el uno del otro.

Cada módulo no necesita saber cómo funciona el otro módulo, en qué idioma está escrito y cómo funciona. A menudo, para organizar la interacción de dichos módulos, se utiliza un determinado contenedor en el que se cargan estos módulos.

Con un diseño adecuado, si cambia un módulo, no tendrá que editar otros, o estos cambios serán mínimos. Cuanto más flojo sea el acoplamiento, más fácil será escribir/comprender/ampliar/reparar el programa.

Se cree que los módulos bien diseñados deben tener las siguientes propiedades:

  • Integridad e integridad funcional : cada módulo implementa una función, pero la implementa bien y por completo, el módulo realiza de forma independiente un conjunto completo de operaciones para implementar su función.
  • Una entrada y una salida : en la entrada, el módulo del programa recibe un determinado conjunto de datos iniciales, realiza un procesamiento significativo y devuelve un conjunto de datos de resultados, es decir, se implementa el principio estándar de IPO - entrada -\u003e proceso -\u003e producción.
  • Independencia lógica : el resultado del trabajo del módulo del programa depende solo de los datos iniciales, pero no depende del trabajo de otros módulos.
  • Vínculos de información débiles con otros módulos : el intercambio de información entre módulos debe minimizarse si es posible.

Es muy difícil para un principiante entender cómo reducir aún más la conectividad de los módulos. En parte, este conocimiento viene con la experiencia, en parte, después de leer libros inteligentes. Pero lo mejor es analizar las arquitecturas de las aplicaciones existentes.

Composición en lugar de herencia

La descomposición competente es un tipo de arte y una tarea difícil para la mayoría de los programadores. La simplicidad es engañosa aquí, y los errores son costosos.

Sucede que los módulos dedicados están fuertemente acoplados entre sí y no pueden desarrollarse de forma independiente. O no está claro de qué función es responsable cada uno de ellos. Si encuentra un problema similar, lo más probable es que la partición en módulos se haya realizado incorrectamente.

Siempre debe quedar claro qué papel juega cada módulo . El criterio más confiable de que la descomposición se realiza correctamente es si los módulos son subrutinas independientes y valiosas que se pueden usar de forma aislada del resto de la aplicación (y, por lo tanto, se pueden reutilizar).

Al descomponer un sistema, es conveniente verificar su calidad haciéndose las siguientes preguntas: "¿Qué tarea realiza cada módulo?", "¿Qué tan fácil es probar los módulos?", "¿Es posible usar los módulos por sí solos?" ¿O en otro entorno? ¿Afectar a los demás?

Debe tratar de mantener los módulos lo más autónomos posible . Como se mencionó anteriormente, este es un parámetro clave para una descomposición adecuada . Por lo tanto, debe realizarse de tal manera que los módulos inicialmente sean débilmente dependientes entre sí. Si tuviste éxito, entonces eres genial.

Si no es así, aquí tampoco está todo perdido. Hay una serie de técnicas y patrones especiales que le permiten minimizar y debilitar aún más los vínculos entre los subsistemas. Por ejemplo, en el caso de MVC, se utilizó el patrón Observer para este propósito, pero son posibles otras soluciones.

Puede decirse que las técnicas de desacoplamiento constituyen el principal "juego de herramientas del arquitecto". Solo es necesario entender que estamos hablando de todos los subsistemas y es necesario debilitar la conexión en todos los niveles de la jerarquía , es decir, no solo entre clases, sino también entre módulos en cada nivel jerárquico.