¿Es problemático tener una dependencia entre objetos de la misma capa en una arquitectura de software en capas?

12

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)?

ingrese la descripción de la imagen aquí

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.

bracco23
fuente
Si no tiene ciclos, en una capa donde tiene los enlaces horysontales existe una división en subcapas que solo tiene dependencias entre capas
max630

Respuestas:

10

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.

Doc Brown
fuente
Continuando con el ejemplo de la interfaz de usuario, ¿cuáles son las ventajas y desventajas de tener las dependencias internas? Puedo ver que facilita la reutilización e introduce dependencias cíclicas, lo que podría ser un problema dependiendo del método DI utilizado. ¿Algo más?
bracco23
@ bracco23: las ventajas y desventajas de tener dependencias "internas" son las mismas que las ventajas y desventajas de tener dependencias arbitrarias. "Contras" son que hacen que los componentes sean más difíciles de reutilizar y más difíciles de probar, especialmente en forma aislada de otros componentes. Los pros son que las dependencias explícitas hacen que las cosas que requieren cohesión sean más fáciles de usar, comprender, administrar y probar.
Doc Brown
14

Me siento cómodo al decir que un objeto que pertenece a una capa puede depender de objetos de capas inferiores

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 1no debe depender de Obj 3. Debería depender, por ejemplo, de, IObj 3y 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, mainque 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.

Pero no estoy seguro de qué pensar sobre los objetos que dependen de otros objetos de la misma capa.

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.

David Arno
fuente
Gracias por la respuesta. Sí, la capa no debe exponer objetos sino interfaces, lo sé, pero lo dejé por razones de simplicidad.
bracco23
4

Veamos esto prácticamente

ingrese la descripción de la imagen aquí

Obj 3ahora sabe que Obj 4existe. ¿Y qué? ¿Por qué nos importa?

DIP dice

"Los módulos de alto nivel no deberían depender de módulos de bajo nivel. Ambos deberían depender de abstracciones".

Bien, pero, ¿no son todos los objetos abstracciones?

DIP también dice

"Las abstracciones no deberían depender de los detalles. Los detalles deberían depender de las 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 3sabe Obj 4existe. ¿Pero Obj 3sabe si Obj 4es concreto?

Esto es por qué es tan agradable NO propagarse por newtodas partes. Si Obj 3no sabe si Obj 4es concreto, probablemente porque no lo creó, entonces si se coló más tarde y se convirtió Obj 4en una clase abstracta Obj 3no le importaría.

Si puedes hacer eso, entonces Obj 4ha 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 que Obj 4es 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 3y Obj 4está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 Iprefijo. 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 Fooembargo, 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 refactoriza Foode 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".

naranja confitada
fuente
0

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

Las dependencias del código fuente deben apuntar solo hacia adentro, hacia políticas de nivel superior.

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 Obj3y Obj4luego, una vez más, no hay nada intrínsecamente incorrecto.

Laiv
fuente