Los sistemas de software
son declaraciones de política. De hecho, todos los programas son una detallada
descripción de una política en los cuales las entradas son transformadas en
salidas.
En sistemas no triviales
la política se puede dividir en distintas partes de manera que habrá ciertas
declaraciones que definen una regla de negocio, el formateo de unos datos, la
validación de unos datos, etc.
Parte del arte de
desarrollar una arquitectura software es separar cuidadosamente aquellas
políticas de otras agrupandolas en base a la manera en la que cambian. Las
políticas que cambian por la misma razón y al mismo tiempo están al mismo nivel
y deberían pertenecer al mismo componente. Las políticas que cambian por
diferentes razones o en diferentes tiempos están en diferentes niveles y
deberían estar separados en distintos componentes.
El arte de la
arquitectura a veces implica agrupar los componentes en un gráfico acíclico
dirigido. Los nodos del gráfico son los componentes que contienen políticas al
mismo nivel. Los bordes dirigidos son las dependencias entre esos
componentes. Estas conectan componentes que están en distintos niveles.
Dependiendo del lenguaje
de programación las dependencias pueden ser import, require, use, etc. Estas
son las dependencias que son necesarias para que funcione el sistema.
En una buena
arquitectura, la dirección de aquellas dependencias está basada en el nivel de
componentes que ellos conectan. En cada caso, los componentes de bajo nivel
están diseñados de manera que estos dependan de los componentes de alto
nivel.
¿Qué es el nivel?
La estricta definición
de nivel es la distancia de los «inputs» y los «outputs». Cuando más lejos esté
la política del los «inputs» y los «outputs» más alto será su nivel. Las
políticas que administran los «inputs» y los «outputs» son las políticas con el
nivel más bajo en el sistema.
El siguiente diagrama
muestra un simple programa de encriptación que lee caracteres desde un dispositivo
de entrada, traduce esos caracteres usando una tabla para más tarde escribir
esos caracteres traducidos en el dispositivo de salida. Los flujos de datos se
muestran como una línea curva sólida. Las dependencias de código fuente
diseñadas propiamente se muestran como líneas discontinuas rectas.
El componente
«Translate» es el componente de más alto nivel dado que es el que más lejos
está de los «inputs» y «outputs».
Se puede apreciar que
los flujos de datos y las dependencias de código no siempre apuntan en la misma
dirección. Esto, de nuevo, es parte de la arquitectura del software. Se quiere
que las dependencias de código fuente estén desacopladas del flujo de datos y
acopladas al nivel.
Sería fácil crear una
arquitectura incorrecta escribiendo el programa de la siguiente manera.
function encrypt() {
while(true)
writeChar(translate(readChar()));
}
|
Esta es una arquitectura
incorrecta dado que la función «encrypt» depende de las funciones de bajo nivel
«WriteChar» y «ReadChar».
Una arquitectura más
flexible podría ser la mostrada en la siguiente figura
Esta arquitectura marca
un límite entre la clase «Encrypt» y las interfaces «Char Reader» y «Char
Writer». Todas las dependencias apuntan hacia el interior del límite. Esta
unidad es la más de más alto nivel en este sistema.
Las clases «Console
Reader» y «Console Writer» son las clases de más bajo nivel dado que son las
que más cerca están a los «inputs» y «outputs».
Se puede apreciar que
con esta estructura se desacopla la política de encriptación de alto nivel con
la política de entrada y salida de bajo nivel. Esto lleva a que la política de
encriptación se pueda usar en un amplio abanico de contextos. Cuando los
cambios se producen en la política de entrada y la salida de datos no influyen
en la política de encriptación.
Hay que recordar que las
políticas se agrupan en componentes según la forma en la que cambian. Las
políticas que cambian por la misma razón y al mismo tiempo deberían estar
juntas por el «Single Responsibility Principle» y «Common Closure
Principle».
Las políticas de alto
nivel tienden a cambiar menos frecuentemente y por razones más importantes que
las políticas de bajo nivel mientras que las políticas de bajo nivel tienden a
cambiar más frecuentemente, con más urgencia y por razones no tan importantes.
Por ejemplo, en el caso
del sistema de encriptación es más probable que cambien los dispositivos de
entrada y salida que el algoritmo de encriptación cambie. Si el algoritmo de
encriptación cambia será más probable que haya una razón sustancial que un
cambio en uno de los dispositivos.
Mantener separadas estas
políticas con todas las dependencias del código apuntando a las políticas de
alto nivel reduce el impacto de los cambios. Los cambios en las políticas de
bajo nivel tendrán poca o ningún impacto en las políticas de alto nivel.
Otra manera de enfocar
este problema es establecer que los componentes de bajo nivel sean plugins de
los componentes de alto nivel. El siguiente diagrama muestra esta
ordenación.
El componente
«Encryption» no debería conocer nada sobre el componente «IO Devices» mientras
que el componente «IO Devices» depende del componente «Encryption».
Conclusión
En este punto, una
discusión de políticas ha envuelto una mezcla de principio de responsabilidad
única (SRP), el principio Abierto-Cerrado (OCP), el principio de cierre común
(CCP), el principio de inversión de dependencias (DIP), el Principio de
Dependencias Estable (SDP) y el Principio de Abstracción Estable (SAP). Se
puede revisar y verificar donde se usa cada principio.
No hay comentarios:
Publicar un comentario