Considerando un software mediano-grande con una arquitectura de n capas e inyección de dependencia, me siento cómodo al decir que un objeto que pertenece a una capa puede depender de objetos de capas inferiores pero nunca de objetos de capas superiores.
Pero no estoy seguro de qué pensar sobre los objetos que dependen de otros objetos de la misma capa.
Como ejemplo, supongamos una aplicación con tres capas y varios objetos como el de la imagen. Obviamente, las dependencias de arriba hacia abajo (flechas verdes) están bien, de abajo hacia arriba (flecha roja) no están bien, pero ¿qué pasa con una dependencia dentro de la misma capa (flecha amarilla)?
Excluyendo la dependencia circular, tengo curiosidad sobre cualquier otro problema que pueda surgir y cuánto se está violando la arquitectura en capas en este caso.
fuente
Respuestas:
Sí, los objetos en una capa pueden tener dependencias directas entre sí, a veces incluso cíclicas; eso es realmente lo que hace la diferencia central de las dependencias permitidas entre objetos en diferentes capas, donde no se permiten dependencias directas o solo una dependencia estricta dirección.
Sin embargo, eso no significa que deban tener tales dependencias de manera arbitraria. Depende realmente de lo que representen sus capas, qué tan grande es el sistema y cuál debe ser la responsabilidad de las partes. Tenga en cuenta que "arquitectura en capas" es un término vago, hay una gran variación de lo que realmente significa en diferentes tipos de sistemas.
Por ejemplo, supongamos que tiene un "sistema de capas horizontales", con una capa de base de datos, una capa empresarial y una capa de interfaz de usuario (UI). Lets dice que la capa de interfaz de usuario contiene al menos varias docenas de clases de diálogo diferentes.
Uno puede elegir un diseño donde ninguna de las clases de diálogo dependa directamente de otra clase de diálogo. Se puede elegir un diseño donde existan "diálogos principales" y "diálogos secundarios" y solo existan dependencias directas de los diálogos "principales" a "secundarios". O uno puede preferir un diseño donde cualquier clase de IU existente pueda usar / reutilizar cualquier otra clase de IU de la misma capa.
Estas son todas las opciones de diseño posibles, tal vez más o menos sensatas según el tipo de sistema que esté creando, pero ninguna de ellas invalida la "estratificación" de su sistema.
fuente
Para ser sincero, no creo que debas sentirte cómodo con eso. Al tratar con cualquier cosa que no sea un sistema trivial, mi objetivo es asegurar que todas las capas solo dependan de abstracciones de otras capas; tanto inferior como superior.
Entonces, por ejemplo,
Obj 1
no debe depender deObj 3
. Debería depender, por ejemplo, de,IObj 3
y se debería decir con qué implementación de esa abstracción trabajará en tiempo de ejecución. Lo que dice no debe estar relacionado con ninguno de los niveles, ya que su trabajo es mapear esas dependencias. Ese podría ser un contenedor de IoC, un código personalizado llamado, por ejemplo,main
que usa DI puro. O en un momento podría incluso ser un localizador de servicios. En cualquier caso, las dependencias no existen entre las capas hasta que esa cosa proporcione la asignación.Yo diría que esta es la única vez que deberías tener dependencias directas. Es parte del funcionamiento interno de esa capa y se puede cambiar sin afectar a otras capas. Entonces no es un acoplamiento dañino.
fuente
Veamos esto prácticamente
Obj 3
ahora sabe queObj 4
existe. ¿Y qué? ¿Por qué nos importa?Bien, pero, ¿no son todos los objetos abstracciones?
Bien, pero si mi objeto está correctamente encapsulado, ¿eso no oculta ningún detalle?
A algunas personas les gusta insistir ciegamente en que cada objeto necesita una interfaz de palabras clave. No soy uno de ellos Me gusta insistir ciegamente en que si no los vas a usar ahora, necesitas un plan para lidiar con la necesidad de algo así más adelante.
Si su código es totalmente refactorizable en cada versión, puede extraer las interfaces más adelante si las necesita. Si ha publicado un código que no desea volver a compilar y se encuentra deseando estar hablando a través de una interfaz, necesitará un plan.
Obj 3
sabeObj 4
existe. ¿PeroObj 3
sabe siObj 4
es concreto?Esto es por qué es tan agradable NO propagarse por
new
todas partes. SiObj 3
no sabe siObj 4
es concreto, probablemente porque no lo creó, entonces si se coló más tarde y se convirtióObj 4
en una clase abstractaObj 3
no le importaría.Si puedes hacer eso, entonces
Obj 4
ha sido completamente abstracto todo el tiempo. Lo único que hace que una interfaz entre ellos desde el principio te dé es la seguridad de que alguien no agregará accidentalmente código que regala queObj 4
es concreto en este momento. Los constructores protegidos pueden mitigar ese riesgo, pero eso lleva a otra pregunta:¿Obj 3 y Obj 4 están en el mismo paquete?
Los objetos a menudo se agrupan de alguna manera (paquete, espacio de nombres, etc.). Cuando se agrupan sabiamente, cambian los impactos más probables dentro de un grupo en lugar de entre grupos.
Me gusta agrupar por característica. Si
Obj 3
yObj 4
están en el mismo grupo y capa, es muy poco probable que haya publicado uno y no quiera refactorizarlo mientras necesita cambiar solo el otro. Eso significa que es menos probable que estos objetos se beneficien de tener una abstracción entre ellos antes de que tenga una clara necesidad.Si está cruzando un límite de grupo, es realmente una buena idea dejar que los objetos en ambos lados varíen independientemente.
Debería ser así de simple, pero desafortunadamente tanto Java como C # han tomado decisiones desafortunadas que complican esto.
En C # es tradición nombrar cada interfaz de palabras clave con un
I
prefijo. Eso obliga a los clientes a SABER que están hablando con una interfaz de palabras clave. Eso interfiere con el plan de refactorización.En Java es tradición usar un mejor patrón de nomenclatura: sin
FooImple implements Foo
embargo, esto solo ayuda a nivel de código fuente ya que Java compila interfaces de palabras clave en un binario diferente. Eso significa que cuando se refactorizaFoo
de clientes concretos a abstractos que no necesitan un solo carácter de código cambiado, aún debe volver a compilarse.Son estos ERRORES en estos idiomas particulares los que evitan que las personas puedan posponer el resumen formal hasta que realmente lo necesiten. No dijo qué idioma está utilizando, pero comprende que hay algunos idiomas que simplemente no tienen estos problemas.
No dijo qué idioma está utilizando, así que lo instaré a que analice su idioma y su situación cuidadosamente antes de que decida que serán interfaces de palabras clave en todas partes.
El principio YAGNI juega un papel clave aquí. Pero también lo hace "Por favor, haz que sea difícil pegarme un tiro en el pie".
fuente
Además de las respuestas anteriores, creo que podría ayudarlo a verlo desde diferentes puntos de vista.
Por ejemplo, desde el punto de vista de la regla de dependencia . DR es una regla sugerida por Robert C. Martin para su famosa Arquitectura limpia .
Dice
Por políticas de nivel superior , se refiere a un nivel superior de abstracciones. Componentes que se filtran en detalles de implementación, como por ejemplo interfaces o clases abstractas en contraste con clases concretas o estructuras de datos.
La cuestión es que la regla no está restringida a la dependencia entre capas. Solo señala la dependencia entre piezas de código, independientemente de la ubicación o la capa a la que pertenecen.
Entonces, no, no hay nada inherentemente malo en tener dependencias entre elementos de la misma capa. Sin embargo, la dependencia aún se puede implementar para transmitir con el principio de dependencia estable .
Otro punto de vista es SRP.
El desacoplamiento es nuestra forma de romper dependencias dañinas y transmitir con algunas mejores prácticas como la inversión de dependencia (IoC). Sin embargo, aquellos elementos que comparten las razones para cambiar no dan razones para desacoplarse porque los elementos con la misma razón para cambiar cambiarán al mismo tiempo (muy probablemente) y también se implementarán al mismo tiempo. Si ese es el caso entre
Obj3
yObj4
luego, una vez más, no hay nada intrínsecamente incorrecto.fuente