¿Es una mala práctica pasar instancias a través de varias capas?


60

En el diseño de mi programa, a menudo llego al punto en que tengo que pasar instancias de objetos a través de varias clases. Por ejemplo, si tengo un controlador que carga un archivo de audio, y luego lo pasa a un reproductor, y el jugador lo pasa al reproductor Ejecutable, que lo pasa de nuevo a otro lugar, etc. Parece un poco malo, pero no saber cómo evitarlo ¿O está bien hacer esto?

EDITAR: Quizás el ejemplo del reproductor no sea el mejor porque podría cargar el archivo más tarde, pero en otros casos eso no funciona.

Respuestas:


54

Como otros han mencionado, esto no es necesariamente una mala práctica, pero debe prestar atención a que no está rompiendo la separación de preocupaciones de las capas y pasando instancias específicas de capa entre capas. Por ejemplo:

  • Los objetos de la base de datos nunca deben pasarse a capas superiores. He visto programas que usan la clase DataAdapter de .NET , una clase de acceso DB, y la pasan a la capa de la interfaz de usuario, en lugar de usar el DataAdapter en el DAL, crear un DTO o un conjunto de datos, y pasarlo. El acceso a la base de datos es el dominio de la DAL.
  • Los objetos de la interfaz de usuario deberían, por supuesto, estar limitados a la capa de la interfaz de usuario. Nuevamente, he visto esto violado, tanto con ListBoxes poblados con datos de usuario pasados ​​a la capa BL, en lugar de una matriz / DTO de su contenido, y (uno de mis favoritos en particular), una clase DAL que recuperó datos jerárquicos de la base de datos, y en lugar de devolver una estructura de datos jerárquica, simplemente creó y completó un objeto TreeView, y lo devolvió a la interfaz de usuario para agregarlo dinámicamente a un formulario.

Sin embargo, si las instancias que está pasando son los DTO o las entidades mismas, probablemente esté bien.


1
Esto puede sonar impactante, pero en los primeros días oscuros de .NET esa era la práctica generalmente recomendada y probablemente era mejor que lo que están haciendo la mayoría de las otras pilas.
Wyatt Barnett

1
Estoy en desacuerdo. Es cierto que Microsoft respaldó la práctica de aplicaciones de una sola capa donde el cliente Winforms también accedió a la base de datos, y el Adaptador de datos se agregó directamente al formulario como un control invisible, pero esa es solo una arquitectura específica, diferente de la configuración de N-niveles del OP . Pero en una arquitectura multicapa, y esto era cierto para VB6 / DNA incluso antes de .NET, los objetos DB permanecían en la capa DB.
Avner Shahar-Kashtan

Para aclarar: ¿ha visto personas que acceden a la interfaz de usuario directamente (en su ejemplo, cuadros de lista) desde su "Capa de datos"? No creo haber encontrado una violación en el código de producción ... wow ...
Simon Whitehead

77
@SimonWhitehead Exactamente. Las personas que estaban confusas acerca de la distinción entre un ListBox y una matriz, y usaron ListBoxes como DTO. Fue un momento que me ayudó a darme cuenta de cuántas suposiciones invisibles que hago que no son intuitivas para los demás.
Avner Shahar-Kashtan

1
@SimonWhitehead: Sí, ya lo he visto en los programas VB6 y VB.NET Framework 1.1 y 2.0 y me encargaron mantener esos monstruos. Se pone muy feo, muy rápido.
jfrankcarr

15

Es interesante que nadie haya hablado sobre objetos inmutables todavía. Yo diría que pasar un objeto inmutable a través de las diferentes capas es realmente algo bueno en lugar de crear muchos objetos de corta duración para cada capa.

Hay algunas buenas discusiones sobre la inmutabilidad de Eric Lippert en su blog.

Por otro lado, diría que pasar objetos mutables entre capas es un mal diseño. Básicamente, está construyendo una capa con la promesa de que las capas circundantes no la alterarán de manera de romper su código.


13

Pasar instancias de objetos es algo normal. Reduce la necesidad de mantener el estado (es decir, las variables de instancia) y desacopla el código de su contexto de ejecución.

Un problema que puede enfrentar es la refactorización, cuando debe cambiar las firmas de varios métodos a lo largo de la cadena de invocación en respuesta a los requisitos de parámetros cambiantes de un método cerca de la parte inferior de esa cadena. Sin embargo, puede mitigarse con el uso de herramientas modernas de desarrollo de software que ayudan en la refactorización.


66
Diría que otro problema que puede enfrentar implica la inmutabilidad. Puedo recordar un error muy desconcertante en un proyecto en el que trabajé donde un desarrollador modificó un DTO sin pensar en el hecho de que su clase en particular no era la única con una referencia a ese objeto.
Phil

8

Tal vez sea menor, pero existe el riesgo de asignar esta referencia en algún lugar de una de las capas, lo que podría causar una referencia colgante o una pérdida de memoria más adelante.


Su punto es correcto, pero según la terminología de OP ("instancias de objetos de paso") tengo la sensación de que o bien está pasando valores (no punteros) o estaba hablando de un entorno recolectado como basura (Java, C #, Python, Go,. ..).
Mohammad Dehghan

7

Si está pasando objetos simplemente porque es necesario en un área remota de su código, el uso de la inversión de control y los patrones de diseño de inyección de dependencia junto con, opcionalmente, un contenedor de IoC apropiado puede resolver los problemas relacionados con el transporte de instancias de objetos. Lo he usado en un proyecto de tamaño mediano y nunca más consideraría escribir un gran código de servidor sin usarlo.


Eso suena interesante, ya uso la inyección de constructor, y creo que mis componentes de alto nivel controlan los componentes de bajo nivel. ¿Cómo se usa un contenedor de COI para evitar transportar casos?
Puckl

Creo que encontré la respuesta aquí: martinfowler.com/articles/injection.html
Puckl

1
Nota al margen, si está trabajando en Java, Guice es realmente agradable, y puede abarcar sus enlaces a cosas como solicitudes para que el componente de alto nivel esté creando el alcance y vinculando las instancias a las clases correctas en ese momento.
Dave

4

Pasar datos a través de un montón de capas no es algo malo, es realmente la única forma en que un sistema en capas puede funcionar sin violar la estructura en capas. La señal de que hay problemas es cuando estás pasando tus datos a varios objetos en la misma capa para lograr tu objetivo.


3

Respuesta rápida: no hay nada de malo en pasar instancias de objetos. Como también se mencionó, el punto es omitir la asignación de esta referencia en todas las capas que potencialmente pueden causar una referencia colgante o pérdidas de memoria.

En nuestros proyectos, utilizamos esta práctica para pasar DTO (objeto de transferencia de datos) entre capas y es una práctica muy útil. También reutilizamos nuestros objetos dto para construir más complejos una vez, como información resumida.


3

Principalmente soy un desarrollador web de interfaz de usuario, pero me parece que su incomodidad intuitiva podría ser menos sobre el paso de la instancia y más sobre el hecho de que va a ser un poco procesal con ese controlador. ¿Debería su controlador estar sudando todos estos detalles? ¿Por qué incluso hace referencia a más del nombre de otro objeto para reproducir el audio?

En el diseño de OOP, tiendo a pensar en términos de lo que es perenne y de lo que es más probable que esté sujeto a cambios. El tema de cambiar cosas es lo que querrás tender a poner en tus cajas de objetos más grandes para que puedas mantener interfaces consistentes incluso cuando los jugadores cambien o se agreguen nuevas opciones. O te encuentras con ganas de intercambiar objetos de audio o componentes al por mayor.

En este caso, su controlador necesita identificar que existe la necesidad de reproducir un archivo de audio y luego tener una forma consistente / perenne de reproducirlo. El material del reproductor de audio, por otro lado, podría cambiar fácilmente a medida que la tecnología y las plataformas se alteran o se agregan nuevas opciones. Todos esos detalles deben ubicarse debajo de una interfaz de un objeto compuesto más grande, IMO, y no debería tener que volver a escribir su controlador cuando cambian los detalles de cómo se reproduce el audio. Luego, cuando pasa una instancia de objeto con los detalles como la ubicación del archivo en el objeto más grande, todo ese intercambio se realiza en el interior de un contexto apropiado donde es menos probable que alguien haga algo tonto con él.

Entonces, en este caso, no creo que sea esa instancia de objeto que se arroje lo que podría estar molestando. Es que el Capitán Picard está corriendo hacia la sala de máquinas para encender el núcleo de urdimbre, corriendo de regreso al puente para trazar las coordenadas, y luego presionando el botón "golpearlo" después de encender los escudos en lugar de simplemente decir "Tomar nosotros al planeta X en Warp 9. Hazlo así ". y dejando que su tripulación solucione los detalles. Porque cuando lo maneja de esa manera, puede capitanear cualquier barco de la flota sin conocer el diseño de cada barco y cómo funciona todo. Y esa es, en última instancia, la mayor victoria de diseño de OOP para disparar, IMO.


2

Es un diseño bastante común que termina sucediendo, aunque puede tener problemas de latencia si su aplicación es sensible a ese tipo de cosas.


2

Este problema se puede resolver con variables de ámbito dinámico, si su idioma las tiene, o almacenamiento local de subprocesos. Estos mecanismos nos permiten asociar algunas variables personalizadas con una cadena de activación o hilo de control, para que no tengamos que pasar estos valores al código que no tiene nada que ver con ellos solo para que puedan comunicarse a otro código que los necesita


2

Como las otras respuestas han señalado, este no es un diseño inherentemente pobre. Puede crear un acoplamiento estrecho entre las clases anidadas y las que las anidan, pero aflojar el acoplamiento puede no ser una opción válida si anidar las referencias proporciona un valor al diseño.

Una posible solución es "aplanar" las referencias anidadas en la clase de controlador.

En lugar de pasar un parámetro varias veces a través de objetos anidados, puede mantener en la clase de controlador referencias a todos los objetos anidados.

Cómo se implementa exactamente esto (o si es una solución válida) depende del diseño actual del sistema, como por ejemplo:

  • ¿Puede mantener algún tipo de mapa de los objetos anidados en el controlador sin que se complique demasiado?
  • Cuando pasa el parámetro al objeto anidado apropiado, ¿puede el objeto anidado reconocer el parámetro de inmediato, o se produjo una funcionalidad adicional al pasarlo a través de los objetos anidados?
  • etc.

Este es un problema que encontré en un patrón de diseño MVC para un cliente GXT. Nuestros componentes GUI contenían componentes GUI anidados para varias capas. Cuando se actualizaron los datos del modelo, terminamos pasándolos a través de varias capas hasta que llegaron a los componentes apropiados. Creó un acoplamiento no deseado entre los componentes de la GUI porque si queríamos que una nueva clase de componente de la GUI aceptara datos del modelo, teníamos que crear métodos para actualizar los datos del modelo en todos los componentes de la GUI que contenían la nueva clase.

Para solucionarlo, mantuvimos en la clase Vista un mapa de referencias a todos los componentes GUI anidados para que cada vez que se actualizaran los datos del modelo, la Vista pudiera enviar los datos del modelo actualizados directamente a los componentes GUI que lo necesitaban, fin de la historia . Esto funcionó bien porque solo había instancias únicas de cada componente de la GUI. Podría ver que no funciona tan bien si hubiera varias instancias de algunos componentes de la GUI, lo que dificulta identificar qué copia necesita actualizarse.


Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.