java - programacion - mvc
Arquitectura limpia: cómo reflejar los cambios de la capa de datos en la interfaz de usuario (2)
Estoy tratando de hacer un diseño basado en la Arquitectura Limpia del Tío Bob en Android.
El problema:
Me gustaría resolver cómo hacer que los cambios generados en un repositorio se reflejen en otras partes de la aplicación, como otros repositorios o Vistas.
El ejemplo
Diseñé un ejemplo MUY simplificado para este ejemplo. Tenga en cuenta que las interfaces de límites se han eliminado para mantener los diagramas pequeños.
Imagine una aplicación que muestre una lista de videos (con título, tamaño y cantidad similar), al hacer clic en un video puede ver los detalles (allí puede agradar / no me gusta el video).
Además, la aplicación tiene un sistema de estadísticas que cuenta la cantidad de videos que le gustaron o no.
Las principales clases para esta aplicación podrían ser:
Para la parte / módulo Videos:
El objetivo
Ahora imagine que verifica sus estadísticas, luego navegue por la lista de videos, abra los detalles de uno y haga clic en el botón Me gusta.
Después de que se envíe al servidor, hay varios elementos de las aplicaciones que deben tener en cuenta el cambio:
- Por supuesto, la vista detallada, debe actualizarse con los cambios (esto se puede hacer a través de devoluciones de llamadas por lo que no hay problema)
- La lista de videos debe actualizar el conteo de "me gusta" para el video dado
- El `StatsRepository puede querer actualizar / invalidar los cachés después de votar un nuevo video
- Si la lista de estadísticas es visible (imagine una pantalla dividida), también debería mostrar las estadísticas actualizadas (o al menos recibir el evento para volver a consultar los datos)
La pregunta
¿Cuáles son los patrones comunes para resolver este tipo de comunicación? Haga su respuesta lo más completa posible, especificando dónde se generan los eventos, cómo se propagan a través de la aplicación, etc.
Nota: se otorgarán recompensas para completar las respuestas
Publicar / Suscribir
Normalmente, para la comunicación n: m (n los remitentes pueden enviar un mensaje a m receptores, mientras que todos los remitentes y receptores no se conocen entre sí) utilizará un patrón de publicación / suscripción . Hay muchas bibliotecas que implementan dicho estilo de comunicación, para Java existe, por ejemplo, una implementación de EventBus en la biblioteca de Guava . Para la comunicación en la aplicación, estas bibliotecas se denominan EventBus o EventManager y eventos de envío / recepción.
Eventos de dominio
Supongamos que ahora ha creado un evento VideoRatedEvent
, que indica que a un usuario le ha gustado o no un video. Este tipo de eventos se conocen como Eventos de dominio . La clase de evento es un POJO simple y podría verse así:
class VideoRatedEvent {
/** The video that was rated */
public Video video;
/** The user that triggered this event */
public User user;
/** True if the user liked the video, false if the user disliked the video */
public boolean liked;
}
Despacho de eventos
Ahora, cada vez que a sus usuarios les gusta o no le gusta un video, deberá enviar un VideoRatedEvent
. Con Guava, simplemente pasará un objeto de evento instanciado para oponerse a EventBus.post(myVideoRatedEvent)
. Idealmente, los eventos se generan en los objetos de su dominio y se envían dentro de la transacción persistente (consulte esta publicación en el blog para obtener más información). Esto significa que a medida que el estado de su modelo de dominio se mantiene, los eventos se envían.
Oyentes del evento
En su aplicación, todos los componentes afectados por un evento ahora pueden escuchar los eventos del dominio. En su ejemplo particular, VideoDetailView
o StatsRepository
pueden ser detectores de eventos para VideoRatedEvent
. Por supuesto, deberá registrarlos en Guava EventBus con EventBus.register(Object)
.
Este es mi 5cents personal y quizás no esté lo suficientemente relacionado con tu ejemplo de "The Clean Architecure".
Normalmente intento forzar una especie de MVC sobre actividades y fragmentos de androides y uso publicar / suscribir para la comunicación. Como componentes, tengo clases de modelo que manejan la lógica de negocios y el estado de los datos. Los métodos de cambio de datos solo deben ser llamados por las clases de controlador, que generalmente es la clase de actividad y también maneja el estado de la sesión. Utilizo fragmentos para gestionar diferentes vistas de partes de la aplicación y vistas bajo esos fragmentos (obviamente). Todos los fragmentos se suscriben a uno o más temas. Utilizo mi propio DataDistributionService simple que maneja diferentes temas, toma mensajes de editores registrados y los retransmite a todos los suscriptores. (parcialmente influenciado por OMG DDS pero MUCHO más primitivo) Una aplicación simple solo tendría un tema único, por ejemplo, "Main".
Cada parte de la interacción de vista (toca, etc.) es manejada por su fragmento primero. El fragmento puede potencialmente cambiar algunas cosas sin enviar notificaciones. Por ejemplo, cambiar el subrango de elementos de datos renderizados si el resto de la aplicación no necesita saber / reaccionar. De lo contrario, el fragmento publica una ViewRequest (...) que contiene los parámetros necesarios para el DDS.
El DDS transmite ese mensaje y en algún momento llega a un controlador. Esto puede ser simplemente la actividad principal o una instancia de controlador específico. Solo debe haber UN controlador para que la solicitud solo se administre una vez. El controlador básicamente tiene una larga lista de códigos de manejo de solicitudes. Cuando llega una solicitud, el controlador llama a la lógica comercial en el modelo. El controlador también maneja otras cosas relacionadas con la vista, como organizar la vista (pestañas) o iniciar diálogos para la entrada del usuario (¿sobreescribir el archivo?) Y otras cosas que el modelo no debe conocer pero influye (Lanzar nueva NoOverWritePermissionException ())
Una vez que se realizan los cambios del modelo, el controlador decide si se debe enviar una notificación de actualización. (por lo general lo hace). De esta forma, las clases modelo no necesitan escuchar o enviar mensajes y solo se ocupan de la lógica de negocios y del estado consistente. La notificación de actualización es emitida y recibida por los fragmentos que luego ejecutan "updateFromModel ()".
Efectos:
Los comandos son globales. Cualquier solicitud de ViewRequest u otro tipo de solicitud se puede enviar desde cualquier lugar al que se pueda acceder al DDS. Los fragmentos no tienen que proporcionar una clase de escucha y ninguna instancia superior tiene que implementar oyentes para sus fragmentos instanciados. Si un nuevo fragmento no requiere nuevas Solicitudes, se puede agregar sin ningún cambio a las clases de controlador.
Las clases modelo no necesitan saber nada acerca de la comunicación. Puede ser lo suficientemente difícil mantener un estado constante y manejar toda la administración de datos. No es necesario el manejo de mensajes ni el manejo del estado de la sesión. Sin embargo, es posible que el modelo no esté protegido contra llamadas malintencionadas desde la vista. Pero ese es un problema general y realmente no se puede evitar si el modelo tiene que dar referencias en algún momento. Si su aplicación está bien con un modelo que solo pasa copias / datos planos es posible. Pero en algún momento el ArrayAdapter simplemente necesita acceso a los mapas de bits que se supone que debe dibujar en la vista de cuadrícula. Si no puede pagar copias, siempre corre el riesgo de que "la vista haga una llamada cambiante al modelo". Diferente campo de batalla ...
Actualizar llamadas puede ser muy simple. Si la actualización de un fragmento es cara (texturas de recarga de fragmentos OpenGL ...), desea tener información de actualización más detallada. El controlador PODRÍA enviar una notificación más detallada, aunque en realidad no debería tener / ser capaz de saber qué partes del modelo cambiaron exactamente. El envío de notas de actualización del modelo es feo. El modelo no solo tiene que implementar mensajes, sino que también se vuelve muy caótico con notificaciones mixtas. El controlador puede dividir notificaciones de actualización y otras un poco mediante el uso de temas. Por ejemplo, un tema específico para los cambios en sus recursos de video. De esta forma, los fragmentos pueden decidir a qué temas se suscriben. Aparte de eso, desea tener un modelo que pueda ser consultado para valores modificados. Timestamp, etc. Tengo una aplicación donde el usuario dibuja formas sobre lienzo. Se procesan en mapas de bits y se utilizan como texturas en una vista de OpenGL. Ciertamente no quiero volver a cargar texturas cada vez que se llama a "updateFromModel ()" en GLViewFragment.
Regla de dependencia
Probablemente no respetado todo el tiempo. Si el controlador maneja un interruptor de tabulación, simplemente puede llamar a "seletTab ()" en un TabHost y, por lo tanto, tiene una dependencia a los círculos externos. Puede convertirlo en un mensaje, pero luego sigue siendo una dependencia lógica. Si la parte del controlador tiene que organizar algunos elementos de la vista (mostrar la pestaña image-editor-fragment automáticamente después de cargar una imagen a través de la galería de imágenes-fragmen-tab) no puede evitar las dependencias por completo. Tal vez pueda hacerlo al modelar viewstate y hacer que sus partes de vista se organicen a sí mismas desde viewstate.currentUseCase o así. Pero si necesita control global sobre la vista de su aplicación, tendré problemas con esta regla de dependencia, diría yo. ¿Qué sucede si intenta guardar algunos datos y su modelo solicita un permiso de sobrescritura? Necesitas crear algún tipo de UI para eso. Dependencia de nuevo Puede enviar un mensaje a la vista y esperar que un DialogFragment lo recoja. Si existe en el mundo extremadamente modular descrito en su enlace.
Entidades:
son las clases modelo en mi enfoque. Eso está muy cerca del enlace que proporcionó.
Casos de uso:
No tengo esos explícitamente modelados por ahora. Atm estoy trabajando en editores para activos de videojuegos. Dibujar formas en un fragmento, aplicar valores de sombreado en otro fragmento, guardar / cargar en un fragmento de galería, exportar a un atlas de textura en otro ... cosas así. Agregaría Casos de uso como algún tipo de subconjunto Solicitud. Básicamente un Caso de uso como un conjunto de reglas que solicitan en qué orden están permitidas / requeridas / esperadas / prohibidas, etc. Las construiría como transacciones para que un Caso de uso pueda seguir progresando, puede terminarse, puede cancelarse e incluso rodarse espalda. Por ejemplo, un caso de uso definiría el orden de guardar una nueva imagen dibujada. Incluyendo la publicación de un cuadro de diálogo para solicitar el permiso de sobrescritura y el retroceso si no se otorga el permiso o el tiempo de espera se alcanza. Pero los casos de uso se definen de muchas maneras diferentes. Algunas aplicaciones tienen un solo caso de uso para una hora de interacción activa del usuario, algunas aplicaciones tienen 50 casos de uso solo para obtener dinero de un cajero automático. ;)
Adaptadores de interfaz:
Aquí se vuelve un poco complicado. Para mí, este parece ser un nivel extremadamente alto para las aplicaciones de Android. Indica "El anillo de adaptadores de interfaz contiene toda la arquitectura MVC de una GUI". Realmente no puedo entender eso. Tal vez estás construyendo aplicaciones mucho más complicadas que yo.
Frameworks y controladores:
No estoy seguro de qué pensar de este. "La web es un detalle, la base de datos es un detalle ..." y el gráfico también contiene "UI" en este anillo. Demasiado para mi pequeña cabeza
Permite verificar el otro "afirma"
Independiente de Frameworks. La arquitectura no depende de la existencia de una biblioteca de software cargado de funciones. Esto le permite utilizar dichos marcos como herramientas, en lugar de tener que ajustar su sistema a sus restricciones limitadas.
Hm, sí, bueno, si ejecutas tu propia arquitectura eso es lo que obtienes.
Testable Las reglas comerciales pueden probarse sin la interfaz de usuario, la base de datos, el servidor web o cualquier otro elemento externo.
Como en mi enfoque, las clases modelo no conocen los controladores ni las vistas, ni tampoco el mensaje que pasa. Se puede probar la coherencia del estado solo con esas clases.
Independiente de UI. La interfaz de usuario puede cambiar fácilmente, sin cambiar el resto del sistema. Una IU web podría reemplazarse con una IU de consola, por ejemplo, sin cambiar las reglas comerciales.
De nuevo un poco exagerado para Android, ¿no es así? Independencia sí. En mi enfoque, puedes agregar o eliminar fragmentos siempre que no requieran un manejo explícito en algún lugar más arriba. Pero reemplazar una interfaz de usuario web con una interfaz de usuario de consola y hacer que el sistema funcione como antes es un sueño húmedo de fanáticos de la arquitectura. Algunos elementos de la interfaz de usuario son parte integral del servicio proporcionado. Por supuesto, puedo cambiar fácilmente el fragmento de dibujo de lienzo por un fragmento de dibujo de consola, o el fragmento de foto clásico para un fragmento de "tomar una foto con consola", pero eso no significa que la aplicación todavía funcione. Técnicamente está bien en mi enfoque. Si implementa un reproductor de video de la consola Ascii, puede reproducir los videos allí y ninguna otra parte de la aplicación necesariamente le importará. Sin embargo, PODRÍA ser que el conjunto de solicitudes que admite el controlador no se alinee bien con la nueva interfaz de usuario de la consola o que un Caso de uso no esté diseñado para el orden en que se debe acceder a un video a través de una interfaz de consola. La vista no es siempre el esclavo sin importancia que a muchos gurús de la arquitectura les gusta ver.
Independiente de la base de datos. Puede intercambiar Oracle o SQL Server, por Mongo, BigTable, CouchDB u otra cosa. Sus reglas comerciales no están ligadas a la base de datos.
¿Si lo? ¿Cómo está directamente relacionado con tu arquitectura? Usa los adaptadores y la abstracción adecuados y puedes tenerlos en una aplicación de Hello World.
Independiente de cualquier agencia externa. De hecho, las reglas de su negocio simplemente no saben nada sobre el mundo exterior.
Igual que aquí. Si desea código independiente modularizado, escríbalo. Es difícil decir algo específico al respecto.