MVVM y patrón de servicio

14

Estoy creando una aplicación WPF usando el patrón MVVM. En este momento, mis modelos de vista llama a la capa de servicio para recuperar modelos (por qué no es relevante para el modelo de vista) y convertirlos en modelos de vista. Estoy usando la inyección del constructor para pasar el servicio requerido al modelo de vista.

Es fácilmente comprobable y funciona bien para modelos de vista con pocas dependencias, pero tan pronto como intento crear modelos de vista para modelos complejos, tengo un constructor con MUCHOS servicios inyectados (uno para recuperar cada dependencia y una lista de todos los valores disponibles para enlazar a un itemSource por ejemplo). Me pregunto cómo manejar múltiples servicios como ese y aún tener un modelo de vista que pueda probar fácilmente en la unidad.

Estoy pensando en algunas soluciones:

  1. Crear un singleton de servicios (IServices) que contenga todos los servicios disponibles como interfaces. Ejemplo: Services.Current.XXXService.Retrieve (), Services.Current.YYYService.Retrieve (). De esa manera, no tengo un gran constructor con una tonelada de parámetros de servicios en ellos.

  2. Creando una fachada para los servicios utilizados por viewModel y pasando este objeto en el ctor de mi viewmodel. Pero entonces, tendré que crear una fachada para cada uno de mis modelos de vista complejos, y podría ser un poco demasiado ...

¿Cuál crees que es la forma "correcta" de implementar este tipo de arquitectura?

alfa-alfa
fuente
Creo que la forma "correcta" de hacerlo es crear una capa separada que llame a los servicios y haga lo necesario para crear el ViewModel. Sus ViewModels no deberían ser responsables de crearse ellos mismos.
Amy Blankenship el
@AmyBlankenship: los modelos de vista no deberían tener (ni necesariamente ser capaces de) crearse a sí mismos, pero inevitablemente a veces serán responsables de crear otros modelos de vista . Un contenedor IoC con soporte automático de fábrica es una gran ayuda aquí.
Aaronaught
"A veces" y "deberían" son dos animales diferentes;)
Amy Blankenship
@AmyBlankenship: ¿Sugiere que los modelos de vista no deberían crear otros modelos de vista? Esa es una píldora difícil de tragar. Puedo entender decir que los modelos de vista no deberían usarse newpara crear otros modelos de vista, pero piense en algo tan simple como una aplicación MDI en la que al hacer clic en un botón o menú de "nuevo documento" se agregará una nueva pestaña o se abrirá una nueva ventana. El shell / conductor debe poder crear nuevas instancias de algo , incluso si está oculto detrás de una o unas pocas capas de indirección.
Aaronaught
Bueno, ciertamente debe tener la capacidad de solicitar que se haga una Vista en algún lugar. Pero para hacerlo por sí mismo? No en mi mundo :). Pero, de nuevo, en el mundo en el que vivo, llamamos a VM "Modelo de presentación".
Amy Blankenship el

Respuestas:

22

De hecho, ambas soluciones son malas.

Crear un singleton de servicios (IServices) que contenga todos los servicios disponibles como interfaces. Ejemplo: Services.Current.XXXService.Retrieve (), Services.Current.YYYService.Retrieve (). De esa manera, no tengo un gran constructor con una tonelada de parámetros de servicios en ellos.

Este es esencialmente el patrón de localización de servicios , que es un antipatrón. Si hace esto, ya no podrá comprender de qué depende realmente el modelo de vista sin mirar su implementación privada, lo que hará que sea muy difícil de probar o refactorizar.

Creando una fachada para los servicios utilizados por viewModel y pasando este objeto en el ctor de mi viewmodel. Pero entonces, tendré que crear una fachada para cada uno de mis modelos de vista complejos, y podría ser un poco demasiado ...

Esto no es tanto un antipatrón, sino un olor a código. Esencialmente, está creando un objeto de parámetro , pero el objetivo del patrón de refactorización de PO es tratar con conjuntos de parámetros que se usan con frecuencia y en muchos lugares diferentes , mientras que este parámetro solo se usaría una vez. Como mencionas, crearía una gran cantidad de código sin beneficio real, y no funcionaría bien con muchos contenedores IoC.

De hecho, ambas estrategias anteriores están pasando por alto el problema general, que es que el acoplamiento es demasiado alto entre los modelos de vista y los servicios . El simple hecho de ocultar estas dependencias en un localizador de servicios u objeto de parámetro no cambia en realidad cuántos otros objetos depende el modelo de vista.

Piense en cómo probaría uno de estos modelos de vista. ¿Qué tan grande será tu código de configuración? ¿Cuántas cosas hay que inicializar para que funcione?

Muchas personas que comienzan con MVVM intentan crear modelos de vista para una pantalla completa , lo que es fundamentalmente el enfoque equivocado. MVVM tiene que ver con la composición , y una pantalla con muchas funciones debe estar compuesta de varios modelos de vista diferentes, cada uno de los cuales depende de uno o unos pocos modelos / servicios internos. Si necesitan comunicarse entre sí, debe hacerlo a través de pub / sub (intermediario de mensajes, bus de eventos, etc.)

Lo que realmente necesita hacer es refactorizar sus modelos de vista para que tengan menos dependencias . Luego, si necesita tener una "pantalla" agregada, cree otro modelo de vista para agregar los modelos de vista más pequeños. Este modelo de vista agregada no tiene que hacer mucho por sí mismo, por lo que también es bastante fácil de entender y probar.

Si ha hecho esto correctamente, debería ser obvio simplemente mirando el código, porque tendrá modelos de vista cortos, concisos, específicos y comprobables.

Aaronaught
fuente
Sí, eso es lo que probablemente terminaré haciendo. Muchas gracias señor.
alfa-alfa el
Bueno, ya asumí que ya había intentado esto, pero no tuvo éxito. @ alfa-alfa
Eufórico el
@Euphoric: ¿Cómo "no tienes éxito" en esto? Como Yoda diría: Haz o no, no hay intentos.
Aaronaught
@Aaronaught Por ejemplo, realmente necesita todos los datos en un solo modelo de vista. Tal vez tiene grilla y diferentes columnas provienen de diferentes servicios. No puedes hacer eso con la composición.
Eufórico el
@Euphoric: En realidad, se puede resolver que con la composición, pero que se puede hacer bajo el nivel de la vista del modelo. Es simplemente una cuestión de crear las abstracciones correctas. En ese caso, solo necesita un servicio para manejar la consulta inicial para obtener una lista de ID, y una secuencia / lista / matriz de "enriquecedores" que anotan con su propia información. Haga que la cuadrícula en sí sea su propio modelo de vista, y ha resuelto el problema con efectivamente dos dependencias, y es extremadamente fácil de probar.
Aaronaught
1

Podría escribir un libro sobre esto ... de hecho lo soy;)

En primer lugar, no existe una forma universalmente "correcta" de hacer las cosas. Tienes que tener en cuenta otros factores.

Es posible que sus servicios sean demasiado finos. Ajustar los servicios con fachadas que proporcionan la interfaz que usaría un modelo de vista específico o incluso un grupo de modelos de vista relacionados podría ser una mejor solución.

Aún más simple sería envolver los servicios en una única Fachada que usan todos los modelos de vista. Por supuesto, puede ser una interfaz muy grande con muchas funciones innecesarias para el escenario promedio. Pero yo diría que no es diferente a un enrutador de mensajes que maneja cada mensaje en su sistema.

De hecho, lo que he visto evolucionar en muchas arquitecturas es un bus de mensajes construido alrededor de algo como el patrón de Agregador de eventos. La prueba es fácil allí porque su clase de prueba solo registra un oyente con el EA y dispara el evento apropiado en respuesta. Pero ese es un escenario avanzado que lleva tiempo crecer. Yo digo comenzar con la fachada unificadora y seguir desde allí.

Michael Brown
fuente
Una fachada de servicio masivo es muy diferente de un agente de mensajes. Está casi en el extremo opuesto del espectro de dependencia. Una característica distintiva de esta arquitectura es que el emisor no sabe nada sobre el receptor, y puede haber muchos receptores (pub / sub o multicast). Quizás lo esté confundiendo con la "comunicación remota" de estilo RPC que simplemente expone un servicio tradicional a través de un protocolo remoto, y el remitente aún está acoplado a la recepción, tanto física (dirección de punto final) como lógicamente (valor de retorno).
Aaronaught
La similitud es que la fachada actúa como un enrutador, la persona que llama no sabe qué servicio / servicios maneja la llamada al igual que un cliente que envía un mensaje no sabe quién maneja el mensaje.
Michael Brown
Sí, pero la fachada es entonces un Objeto de Dios . Tiene todas las dependencias que tienen los modelos de vista, probablemente más porque será compartido por varios. En efecto, ha eliminado los beneficios del acoplamiento suelto por el que trabajó tan duro en primer lugar, porque ahora, cada vez que algo toca la megafachada, no tiene idea de qué funcionalidad realmente depende. Imagen escribiendo una prueba de unidad para una clase que usa la fachada. Creas un simulacro para la fachada. Ahora, ¿qué métodos te burlas? ¿Cómo se ve su código de configuración?
Aaronaught
Esto es muy diferente de un agente de mensajes porque el agente tampoco sabe nada sobre la implementación de los controladores de mensajes . Utiliza IoC debajo del capó. La fachada sabe todo sobre los destinatarios porque tiene que reenviarles las llamadas. El bus tiene cero acoplamiento; la fachada tiene un acoplamiento eferente obscenamente alto. Casi todo lo que cambie, en cualquier lugar, afectará también la fachada.
Aaronaught
Creo que parte de la confusión aquí, y lo veo bastante, es lo que significa una dependencia . Si tiene una clase que depende de otra clase, pero llama a 4 métodos de esa clase, tiene 4 dependencias, no 1. Poner todo detrás de una fachada no cambia el número de dependencias, solo las hace más difíciles de entender. .
Aaronaught
0

¿Por qué no combinar ambos?

Cree una fachada y ponga todos los servicios que usan sus modelos de vista. Entonces puede tener una fachada única para todos sus modelos de vista sin la mala palabra S.

O puede usar la inyección de propiedades en lugar de la inyección del constructor. Pero entonces, debe asegurarse de que se inyecten correctamente.

Eufórico
fuente
Esta sería una mejor respuesta si proporcionara un ejemplo en pseudo-C #.
Robert Harvey, el