2012-06-22 11 views
8

Estoy construyendo una aplicación similar a Visual Studio en WPF y tengo algunos problemas para identificar la mejor organización de diseño arquitectónico de mis componentes. Planeo usar Unity como mi contenedor de inyección de dependencias y el marco de prueba de la unidad de Visual Studio y, probablemente, moq para burlarse de la biblioteca.MVVM con diseño arquitectónico Unity and Unit Testing

voy a describir primero la estructura de mi solución, entonces mis preguntas:

Tengo un proyecto WPF que contiene:

  • Mi inicialización contenedor Unidad (programa previo) al iniciar la aplicación (en App.xaml.cs)
  • Todas mis vistas de aplicaciones (XAML).

otro proyecto llamado modelo de vista este contiene:

  • Todos mis ViewModels aplicación. Todos mis ViewModels heredan de una ViewModelBase que expone una propiedad ILogger

Mi lógica de inicialización es el siguiente:

  1. Aplicación de inicio
  2. Unidad creación de contenedores y el registro de los tipos: MainView y MainViewModel
  3. Resuelve mi MainView y muéstralo.

var window = Container.Resolve<MainView>();

window.Show();

Mi constructor MainView recibe un objeto MainViewModel en su constructor:

public MainView(MainViewModel _mvm) 
  1. Mi MainViewModel tiene un modelo de vista del niño para cada uno de sus paneles:

    public ToolboxViewModel ToolboxVM{get; set;} 
    public SolutionExplorerViewModel SolutionExplorerVM { get; set; } 
    public PropertiesViewModel PropertiesVM { get; set; } 
    public MessagesViewModel MessagesVM { get; set; } 
    

Y tengo la intención de crear un método InitializePanels() que inicializa cada uno de los paneles.

Ahora aquí mis preguntas: ¿Cómo puede mi MainViewModel.InitializePanels() inicializar todos esos paneles? Dadas las siguientes opciones:

Opción 1: inicializar el ViewModels manualmente:

ToolboxVM = new ToolboxViewModel(); 
//Same for the rest of VM... 

Contras:

  • no estoy usando el contenedor de la unidad para mis dependencias (por ejemplo,ILogger) no se resuelve automáticamente

Opción 2: Uso de inyección colocador anotando mis propiedades:

[Dependency] 
public ToolboxViewModel ToolboxVM{get; set;} 
//... Same for rest of Panel VM's 

Contras:

  • He leído que las dependencias de la Unidad Setter debe evitarse ya que generan una dependencia con Unity en este caso
  • También leí que debe evitar el uso de Unity para las pruebas unitarias, entonces, ¿cómo hacer que esta dependencia quede clara en mis pruebas unitarias? Tener muchas propiedades dependientes podría ser una pesadilla para configurar.

Opción 3: Uso Unidad Constructor de inyección para pasar todos mis ViewModels panel al constructor MainViewModel por lo que se resuelven automáticamente por la Unidad de contenedores:

public MainViewModel(ToolboxViewModel _tbvm, SolutionExploerViewModel _sevm,....) 

Pros:

  • La dependencia sería evidente y clara en el momento de la creación, lo que podría ayudar a crear mis UnitTests de ViewModel.

Contras:

  • tener tantos parámetros del constructor podrían ponerse feas con bastante rapidez

Opción 4: El registro de todos los tipos de máquinas virtuales en mis acumulación de contenedores. Luego de pasar la instancia UnityContainer través de la inyección de constructor a mi MainViewModel:

public MainViewModel(IUnityContainer _container) 

De esa manera podría hacer algo como:

 Toolbox = _container.Resolve<ToolboxViewModel>(); 
     SolutionExplorer = _container.Resolve<SolutionExplorerViewModel>(); 
     Properties = _container.Resolve<PropertiesViewModel>(); 
     Messages = _container.Resolve<MessagesViewModel>(); 

Contras:

  • Si decide no utilizar Unity para mis UnitTests, como muchas personas sugieren, entonces no podré resolver e inicializar mi Panel ViewModels.

Teniendo en cuenta esa larga explicación, ¿cuál es el mejor enfoque para poder aprovechar un Contenedor de Inyección de Dependencia y terminar con una solución Testable por la Unidad?

Gracias de antemano,

+0

Tiene toda la razón. Debería codificar contra interfaces y no con implementaciones concretas, sin embargo, normalmente empiezo a codificar las clases concretas y luego extraigo sus interfaces con Resharper, todavía no he llegado a ese punto, ¡pero lo haré pronto! –

+0

Mi solución rápida y sucia para esto es crear una instancia de mis modelos de vista en app.xaml como recursos, luego combinarlos según sea necesario. '' que hace que las pruebas unitarias sean fáciles de configurar. – Will

Respuesta

5

Lo primero es lo primero ... Como habrás notado, tu configuración actual puede ser problemática cuando pruebas la unidad (inicialización de VM compleja). Sin embargo, simplemente siguiendo DI principle, depende de las abstracciones, no de las concreciones, hace que este problema desaparezca inmediatamente. Si sus modelos de vista implementarían interfaces y las dependencias se realizarían a través de interfaces, cualquier inicialización compleja se vuelve irrelevante ya que en la prueba simplemente usará simulaciones.

A continuación, el problema con las propiedades anotadas es que crea alto acoplamiento entre su modelo de vista y Unity (por eso es más probable que esté mal). Idealmente, los registros deben manejarse en un solo punto de nivel superior (que es el programa de arranque en su caso), por lo que el contenedor no está obligado de ninguna manera a oponerse. Su opciones # 3 y # 4 son las soluciones más comunes para este problema, con pocas notas:

  • a # 3: demasiadas dependencias del constructor suele ser mitigado mediante la agrupación de funcionalidad común en facade classes (sin embargo 4 es no que muchos después de todo). Generalmente, el código diseñado correctamente no tiene este problema. Tenga en cuenta que, dependiendo de lo que haga su MainViewModel, tal vez todo lo que necesite es una dependencia de lista de modelos de vista infantil, no concretos.
  • a # 4: no debe usar el contenedor IoC en pruebas unitarias. Usted simple crea su MainViewModel (vía ctor) manualmente e inyecta burlas a mano.

Me gustaría abordar un punto más. Considere lo que sucede cuando su proyecto crece. Embalaje todos ver modelos en un solo proyecto podría no ser una buena idea. Cada modelo de vista tendrá sus propias dependencias (a menudo sin relación con los demás), y todas estas cosas tendrán que sentarse juntas. Esto puede volverse difícil de mantener rápidamente. En su lugar, piense si puede extraer algunas funcionalidades comunes (por ejemplo, mensajes, herramientas) y tenerlas en grupos separados de proyectos (nuevamente, divididos en proyectos M-VM-V).

Además, es mucho más fácil intercambiar vistas cuando tiene una agrupación relacionada con la funcionalidad. Si la estructura del proyecto es el siguiente:

> MyApp.Users 
> MyApp.Users.ViewModels 
> MyApp.Users.Views 
> ... 

probando diferentes vistas de la ventana de edición de usuario es una cuestión de volver a compilar e intercambiar montaje sencillo (User.Views). Con enfoque todo en una bolsa, tendrá que reconstruir una parte mucho más grande de la aplicación, incluso aunque la mayoría no haya cambiado en absoluto.

Editar: tener en cuenta que el cambio de estructura del proyecto (incluso uno pequeño) existente, es por lo general un proceso muy costoso con los resultados del negocio Leves/ilesos. Puede que no esté permitido o que simplemente no pueda permitirse hacerlo.Uso -based (DAL, BLL, BO, etc.) estructura funciona, simplemente se vuelve más pesado con el tiempo. También puede utilizar el modo mixto, con funcionalidades principales agrupadas por su uso y simplemente agregando nuevas funcionalidades utilizando el enfoque modular.

+1

Hombre, he leído su respuesta como 10 veces, cada vez que tiene más sentido. Es una increíble revelación. La construcción de proyectos separados por 'Módulo' tiene sentido, pero ya tengo MUCHOS proyectos como DAL (Capa de Acceso a Datos), Común, BLL (Capa Lógica de Negocios), Objetos de Negocio, Proyecto de Prueba, etc ... –

+1

@AdolfoPerez: si ese es el En este caso, debe considerar el tiempo y el esfuerzo invertidos en * cambiar * la infraestructura existente. Si no puedes pagar eso, simplemente no hagas eso - las estructuras de "bolsas" * también funcionan, solo que pueden ser un poco más difíciles de mantener con el tiempo. Lo he agregado a mi respuesta para que no haya malentendidos. –

+0

De hecho, estoy en las primeras etapas de diseño/desarrollo, por lo que no tendría un impacto muy negativo para mover las cosas, solo necesito estar cómodo de la manera en que quiero ir. Gracias de nuevo por su comprensión. –

2

En primer lugar, es probable que querría utilizar las interfaces en lugar de clases concretas, de modo que usted será capaz de pasar objetos simulados que cuando la unidad de pruebas, es decir, en lugar de IToolboxViewModelToolboxViewModel, etc.

Dicho esto, recomendaría la tercera opción - inyección de constructor.Esto tiene más sentido, ya que de lo contrario podría llamar al var mainVM = new MainViewModel() y terminar con un modelo de vista no funcional. Al hacer eso, también hace que sea muy fácil entender cuáles son las dependencias de su modelo de vista, lo que hace que sea más fácil escribir pruebas unitarias.

Revisaría this link, ya que es relevante para su pregunta.

+0

Gracias Lester, sí, creo que la opción 3 también tiene más sentido, especialmente cuando se agrupan parámetros de constructor a través de clases de fachada como se sugirió @jimmy_keen. ¡Gracias por tu ayuda! Esperará para escuchar lo que otras personas sugieren –

1

Estoy de acuerdo con los puntos de Lester, pero quería agregar algunas otras opciones y opiniones.

Donde está pasando el ViewModel a la Vista a través del constructor, esto es un poco poco convencional ya que la capacidad de enlace de WPF le permite desacoplar completamente el ViewModel de la Vista vinculando al objeto DataContext. En el diseño que ha delineado, la Vista se combina con una implementación concreta y limita la reutilización.

Mientras que una fachada de servicio simplificará la opción 3, no es raro (como se ha descrito) que los ViewModels de alto nivel tengan muchas responsabilidades. Otro patrón que puede considerar es un patrón de controlador o fábrica que ensambla el modelo de vista. La fábrica puede ser respaldada por el contenedor para hacer el trabajo, pero el contenedor se abstrae de la persona que llama. Un objetivo clave en la creación de aplicaciones basadas en contenedores es limitar el número de clases que comprenden cómo se ensambla el sistema.

Otra preocupación es la cantidad de responsabilidades y relaciones de objeto que pertenecen al modelo de vista de nivel superior. Si observa Prism (un buen candidato con WPF + Unity) introduce el concepto de "regiones" que están pobladas por módulos. Una región puede representar una barra de herramientas que está poblada por módulos mutliple. Bajo dicho diseño, el modelo de vista de nivel superior tiene menos responsabilidades (¡y dependencias!) Y cada módulo contiene componentes DI comprobables por la Unidad. Gran cambio en el pensamiento a partir del ejemplo que ha proporcionado.

En cuanto a la opción 4, donde el contenedor se pasa a través del constructor es técnicamente inversión de la dependencia, pero tiene la forma de ubicación de servicio en lugar de inyección de dependencia. Después de haber recorrido este camino antes puedo decir que es una pendiente muy resbaladiza (más como un acantilado): las dependencias están ocultas dentro de las clases y tu código se convierte en una red de locura "justo a tiempo": completamente impredecible, totalmente imposible de evaluar.

+0

Gracias por su comentario @bryanbcook. De hecho, PRISM fue una de mis opciones de infraestructura, revisé algunos ejemplos sobre regiones y módulos, pero creo que implica una curva de aprendizaje mucho más costosa para obtener una comprensión profunda del marco PRISM. –

+0

Prism es solo un ejemplo de que las responsabilidades se pueden desglosar aún más. – bryanbcook