martes, 13 de octubre de 2009

¿MVP e IoC trabajando juntos? ¡Pues claro!

Un comentario de Galcet en mi post “Como independizar tu capa lógica de tu capa de presentación” decía que el entendía por separado los conceptos de IoC y los de MVC pero que no veía como podían trabajar juntos… El motivo de este post es para comentar precisamente esto: no sólo cómo MVC e IoC pueden trabajar juntos sinó las ventajas que la combinación de ambos patrones nos aporta.

Galcet no comentaba si se refería a aplicaciones desktop o web. En este post voy a tratar aplicaciones de escritorio (por lo que me centraré en el patrón MVP más que en el MVC dado que, en mi opinión, MVP aplica mejor que MVC en aplicaciones desktop). En aplicaciones web, si usamos ASP.NET MVC el tema se simplifica mucho, dado que ASP.NET MVC está “preparado” para que sea muy fácil crear nuestros controladores mediante cualquier contenedor IoC.

La filosofía CAB + SCSF

CAB (o Composite UI Application Block) es un framework para el desarrollo de aplicaciones de escritorio winforms con interfaz de usuario compleja. Se basa en el patrón MVP y se compone de varios assemblies y una guía de buenas prácticas. Aunque puede usarse sola, suele combinarse con SCSF (Smart Client Software Factory), un conjunto de guías, librerías y buenas prácticas para la creación de aplicaciones winforms complejas. La recomendación de SCSF para la interfaz de usuario es usar CAB, y de hecho extiende CAB. No voy a hablar aquí ni de CAB ni de SCSF (hay varios tutoriales en internet), sino de como CAB y SCSF afrontan el uso de IoC junto con MVP.

La filosofía de SCSF es que nosotros creamos las vistas y las vistas crean a su presenter asociado. El código equivalente usando Unity sería algo parecido a:

public class View : UserControl, IView
{
public View ()
{
InitializeComponents();
// Resto de código...
}
private IMyPresenter _presenter;
[Dependency()]
public IMyPresenter Presenter
{
get { return _presenter;}
set
{
_presenter = value;
_presenter.View = this;
}
}
}
public class MyPresenter : IPresenter
{
public IView View { get; set;}
}

El contenedor debe tener registrado el mapping entre las interfaces y las clases:


container.RegisterType<IPresenter, MyPresenter>();

container.RegisterType<IView, View>();
// Creamos la vista
var view = container.Resolve<IView>();


Al llamar al método Resolve, Unity consulta sus mappings y llega a la conclusión de que debe crear un objeto View. La clase View tiene una propiedad “Presenter” decorada con [Dependency()], por lo que Unity debe inyectar un valor a esta propiedad. La propiedad es de tipo IMyPresenter, Unity consulta sus mappings y vee que debe crear un objeto de la clase MyPresenter. Luego en el setter de la propiedad “Presenter” la vista se asigna a si misma como vista del presenter recién creado.


A lo mejor alguien se pregunta porque no usamos inyección de dependencias en el constructor del presenter, es decir que en lugar de que nuestro presenter declare una propiedad que reciba la vista y rellenar esta propiedad desde la propia vista, declaramos la vista en el constructor y que Unity se encargue de todo:


public class View : UserControl, IView
{
public View ()
{
InitializeComponents();
// Resto de código...
}
private IMyPresenter _presenter;
[Dependency()]
public IMyPresenter Presenter { get; set;}
}
public class MyPresenter : IPresenter
{
public MyPresenter (IView view)
{
// Nos guardamos la vista...
}
}


¿Qué problema hay en este código? A priori puede parecer que ninguno, pero si repasamos como actuaría Unity:



  1. Al llamar a Resolve<IView> Unity consulta sus mappings y ve que debe crear un objeto de la clase View
  2. Al inyectar la propiedad Presenter, Unity consulta sus mappings y ve que debe crear un objeto de la clase MyPresenter
  3. Al intentar crear un objeto MyPresenter, Unity observa que el constructor recibe un IView y que debe inyectarlo.
  4. Así pues, Unity consulta sus mappings y crea un nuevo objeto View que inyectará en el constructor del MyPresenter.

Suponiendo que todo esto no terminase en un Stack Overflow, en todo caso el objeto MyPresenter recibiría un objeto View distinto del que devolvería la llamada a Resolve.


El “problema” de Visual Studio


¿Porque ha optado CAB por esta filosofía? ¿Porque las vistas crean a los presenters y no al revés? ¿Porque la inyección de dependencias es por propiedades y no en el constructor? La respuesta a todos estos interrogantes se llama Visual Studio.


Me explico: si tenemos una vista (o sea un UserControl) llamada View, cuando la arrastramos dentro de un formulario, o de un panel Visual Studio genera un código parecido a:


View view1 = new View();
panel1.Controls.Add(view1);


¿Observáis el problema? Visual Studio ha creado una instancia de la vista, usando new, no usando el método Resolve del contenedor de IoC, por lo tanto nos podemos olvidar de la inyección de dependencias, por lo que efectivamente nuestro presenter no estará creado.


¿Como podemos solucionar este problema? Bueno… todos los controladores IoC permiten “inicializar” un objeto ya creado, entendiendo por “inicializar” inyectar todas las dependencias que este objeto necesita (en el caso de nuestras vistas, la propiedad “Presenter”). En el caso de Unity este método se llama BuildUp, y se le pasa la instancia del objeto a inicializar. Lo que debemos hacer es inicializar todos los controles que estén en el formulario, lo que podemos hacer en el método Main:


[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Form1 frm = new Form1();
IUnityContainer container = new UnityContainer();
frm.BuildUpControls(container);
Application.Run(frm);
}


Donde el método “BuildUpControls” es un método de extensión definido de la siguiente manera:


public static class FormExtensions
{
public static void BuildUpControls (this Control self,
IUnityContainer container)
{
container.BuildUp(self);
foreach (Control control in self.Controls)
{
control.BuildUpControls(container);
}
}
}


El método BuildUpControls va recorriendo recursivamente la colección “Controls” para llamar al método “BuildUp” del contenedor con todos los controles creados. En este caso no miramos nada más, por lo que inicializamos todos los controles (incluso las labels, los textboxes…), lo que es excesivo. Un refinamiento es inicializar sólo aquellos controles que sean “vistas”. Por ejemplo, CAB para saber que un control es una “vista” y que debe ser inicializado obliga a decorarlo con el atributo [SmartPart].


Evidentemente si nosotros mismos añadimos en run-time una vista debemos inicializarla “manualmente”:


Subview view = new Subview();
view.BuildUpControls(container);
// O bien...
Subview view = container.Resolve<Subview>();


Eso mismo ocurre en CAB: si en CAB creamos una vista de forma programática también debemos “inicializarla”. CAB gestiona la inicialización de una forma totalmente distinta, pero la filosofía es la misma (que es lo que intento contar).


Pues bueno… hemos visto como podemos combinar el uso de un contenedor IoC (como siempre en mi caso Unity :p) junto con el patrón MVP. Fijaos que los presenters si que están creados por Unity, por lo que pueden recibir dependencias inyectadas en el constructor (p.ej. a un servicio de log).


Un saludo!


PD: Alguien dudaba de que esto fuera un crosspost desde mi blog en geeks.ms? ;-)

No hay comentarios: