Pregunta MVVM y IOC: manejo de los invariantes de clase del modelo de visualización


Este es un problema con el que he estado luchando desde que comencé a usar MVVM, primero en WPF y ahora en Silverlight.

Utilizo un contenedor IOC para administrar la resolución de Vistas y Modelos de Vista. Las vistas tienden a ser muy básicas, con un constructor predeterminado, pero ViewModels tiende a acceder a servicios reales, todos los cuales son necesarios para su construcción. De nuevo, utilizo un contenedor IOC para la resolución, por lo que la inyección de servicios no es un problema.

Lo que se convierte en un problema es pasar los datos requeridos a ViewModel usando IOC. Como un ejemplo simple, considere una pantalla que permite la edición de un cliente. Además de los servicios que pueda necesitar, ViewModel para esta pantalla requiere un objeto del cliente para mostrar / editar los datos del cliente.

Al hacer cualquier tipo de desarrollo de biblioteca (que no sea MVVM), considero que es una regla inamovible que los invariantes de clase se pasen a través del constructor. En los casos en que necesito datos específicos del contexto para el tiempo de construcción de la clase y la clase en cuestión es administrada por contenedor, tiendo a usar una fábrica abstracta * como un puente. En MVVM esto parece exagerado, ya que la mayoría de ViewModels requerirá su propia fábrica.

Algunos otros enfoques que he intentado / considerado incluyen (1) un método de inicialización / carga en el que paso los datos, que viola la regla de forzar invariantes de clase a través del constructor, (2) pasar datos a través del contenedor como reemplazos de parámetros (Unity ), y (3) pasar datos a través de una bolsa de estado global (ugh).

¿Cuáles son algunas formas alternativas de pasar datos específicos del contexto de un ViewModel al siguiente? ¿Alguno de los marcos MVVM aborda este problema específico?

*que puede tener sus propios problemas, como requerir una elección entre una llamada a Container.Resolve () o no tener su ViewModel manejado por contenedor. Castle Windsor tiene una buena solución para esto, pero AFAIK no tiene otro marco.

Editar:

Olvidé agregar: algunas de las opciones que enumeré ni siquiera son posibles si está haciendo MVVM "Ver primero", a menos que primero transfiera datos a la Vista y luego a ViewModel.


29
2018-06-07 15:09


origen


Respuestas:


Solía ​​luchar mucho en este tema. Hasta donde puedo decir, no hay otros enfoques viables; parece que ya has reflexionado profundamente sobre el asunto por ti mismo. Solo quiero que dos agreguen mi dos 0.5 centavos en el razones por qué a menudo elijo la opción (1):

  1. el método init es más fácil de implementar que cualquier otra opción (bueno, Windsor's Typed Factory es igual de fácil);
  2. la debilidad de diseño de no tener un parámetro contructor podría mitigarse imponiendo una verificación de los parámetros de inicialización más adelante en el ciclo de vida de la VM
  3. el "lugar" al que llamarías el método init es el mismo en el que habrías llamado al constructor (o a la fábrica abstracta);
  4. a diferencia de la fábrica abstracta, puede factorizar el método init en una interfaz específica para manejar varias máquinas virtuales en una ruta de herencia diferente (si es necesario);
  5. es un compromiso justo (al menos en este contexto): si realmente no puede vivir con eso, simplemente busque la solución de fábrica sin preocuparse por la (muy poca) sobrecarga de complejidad.

5
2018-06-07 15:59



No estoy seguro de cuál es el problema, así que usaré un ejemplo simple y artificial.

Digamos que tienes un CustomerListViewModel que enumera un resumen de cada cliente. Cuando selecciona un cliente, desea mostrar un CustomerDetailViewModel. Esto podría tomar una ID de cliente o una ICustomer tipo que se llenó previamente en el CustomerListViewModel con los detalles del cliente (dependiendo de cuándo quiera cargar los datos, por ejemplo).

Creo que lo que estás preguntando es qué pasa si CustomerDetailViewModel también toma una serie de servicios como dependencias que desea resolver a través del contenedor (normalmente para cadenas de dependencia).

Como primero está haciendo el modelo de vista, necesita crear una instancia del CustomerDetailViewModel desde el CustomerListViewModel, y desea hacerlo a través del contenedor para que las dependencias se inyecten adecuadamente.

Por lo tanto, como mencionas, normalmente harías esto a través de un patrón abstracto de fábrica, por ejemplo ICustomerDetailViewModelFactory que se pasa como un servicio a la CustomerListViewModel.

Este tipo de fábrica tiene una ICustomerDetailViewModel GetCustomerDetailViewModel(ICustomer customer) método. Este tipo de fábrica requerirá una referencia a su contenedor IoC.

Al resolver el ICustomerDetailViewModel en tus GetCustomerDetailViewModel método de fábrica, puede especificar el valor que desea usar para el parámetro constructor ICustomer cuando llama a Resolve en su contenedor.

Por ejemplo, Unity tiene anulaciones de parámetros, y ver aquí para el apoyo de Castle Windsor. Castle Windsor también tiene un instalación de fábrica mecanografiada, para que no necesites implementar los tipos de fábrica de concreto, solo las abstracciones.

¡Así que iría con la opción 2! Usamos Caliburn.Micro, resuelve muchos problemas MVVM, pero no conozco ningún framework que resuelva este problema.


7
2018-06-07 15:38



No estoy tan seguro de que MVVM e IoC se presten a tener invariantes de clase en constructores. En mi experiencia, ViewModels se crean como resultado de un ICommand.Execute, que permite un proceso simple de dos etapas:

var vm = Container.Resolve<CustomerViewModel>();
vm.Model = CustomerRepository.GetCustomerModel(id);

En esta etapa, mi View no tiene conocimiento del ViewModel, y eso solo ocurrirá cuando inyecte el ViewModel en cualquier contenedor vinculado a la Vista. También estoy usando DataTemplates para renderizar ViewModel, lo que significa que no tengo que instanciar una vista directamente para suministrar un DataContext.

Entonces, para responder, usaría (1) y rompería la "regla".


2
2018-06-07 15:39