abstracción es buena, pero es importante recordar que en algún momento algo tiene que saber una cosa o dos acerca de una cosa o dos, o de lo contrario sólo tendremos que Tenga una pila de legos bien dibujados sentados en el piso en vez de que estén ensamblados en una casa.
Un contenedor de inversión de control/inyección de dependencia/flippy-dippy-upside-down-whatever-we-are-calling-it-this-week como Autofac realmente puede ayudar a unir todo esto.
Cuando tiro juntos una aplicación de Windows Forms, por lo general terminan con un patrón repetitivo.
Comenzaré con un archivo Program.cs
que configura el contenedor Autofac y luego obtiene una instancia del MainForm
y muestra el MainForm
.Algunas personas lo llaman el shell o el espacio de trabajo o el escritorio, pero en cualquier caso es "el formulario" que tiene la barra de menú y muestra ventanas secundarias o controles de usuario secundarios, y cuando se cierra, la aplicación sale.
Siguiente es el MainForm
antes mencionado. Hago las cosas básicas, como arrastrar y soltar algunos SplitContainers
y MenuBar
s y tal en el diseñador visual de Visual Studio, y luego empiezo a obtener un código sofisticado: voy a tener ciertas interfaces clave "inyectadas" en el constructor MainForm
para que pueda hacer uso de ellos, para que mi MainForm pueda orquestar controles secundarios sin tener que saber mucho sobre ellos.
Por ejemplo, podría tener una interfaz IEventBroker
que permite que varios componentes publiquen o se suscriban a "eventos" como BarcodeScanned
o ProductSaved
. Esto permite que partes de la aplicación respondan a los eventos de una manera débil, sin tener que depender del cableado de eventos .NET tradicionales. Por ejemplo, el EditProductPresenter
que va junto con mi EditProductUserControl
podría decir this.eventBroker.Fire("ProductSaved", new EventArgs<Product>(blah))
y el IEventBroker
verificaría su lista de suscriptores para ese evento y llamaría a sus devoluciones de llamada. Por ejemplo, el ListProductsPresenter
podría escuchar ese evento y actualizar dinámicamente el ListProductsUserControl
al que está conectado. El resultado neto es que si un usuario guarda un producto en un control de usuario, el presentador de otro usuario puede reaccionar y actualizarse si está abierto, sin que el control tenga que conocer su existencia y sin que el MainForm
tenga que orquestar ese evento.
Si estoy diseñando una aplicación MDI, que tenga la MainForm
implementar una interfaz que tiene IWindowWorkspace
Open()
y Close()
métodos. Podría inyectar esa interfaz en mis varios presentadores para permitirles abrir y cerrar ventanas adicionales sin que ellos estén al tanto del MainForm
directamente. Por ejemplo, el ListProductsPresenter
podría querer abrir un EditProductPresenter
y EditProductUserControl
correspondiente cuando el usuario hace doble clic en una fila en una cuadrícula de datos en un ListProductsUserControl
. Puede hacer referencia a IWindowWorkspace
, que en realidad es MainForm
, pero no es necesario que lo sepa, y llame al Open(newInstanceOfAnEditControl)
y suponga que el control se mostró en el lugar apropiado de la aplicación de alguna manera. (La aplicación MainForm
sería, presumiblemente, cambiar el control a la vista en un panel en alguna parte.)
Pero, ¿cómo diablos la ListProductsPresenter
crear esa instancia de la EditProductUserControl
? Autofac's delegate factories son una verdadera alegría aquí, ya que sólo se puede inyectar un delegado en el presentador y Autofac automágicamente cablear como si se tratara de una fábrica (pseudocódigo siguiente):
public class EditProductUserControl : UserControl
{
public EditProductUserControl(EditProductPresenter presenter)
{
// initialize databindings based on properties of the presenter
}
}
public class EditProductPresenter
{
// Autofac will do some magic when it sees this injected anywhere
public delegate EditProductPresenter Factory(int productId);
public EditProductPresenter(
ISession session, // The NHibernate session reference
IEventBroker eventBroker,
int productId) // An optional product identifier
{
// do stuff....
}
public void Save()
{
// do stuff...
this.eventBroker.Publish("ProductSaved", new EventArgs(this.product));
}
}
public class ListProductsPresenter
{
private IEventBroker eventBroker;
private EditProductsPresenter.Factory factory;
private IWindowWorkspace workspace;
public ListProductsPresenter(
IEventBroker eventBroker,
EditProductsPresenter.Factory factory,
IWindowWorkspace workspace)
{
this.eventBroker = eventBroker;
this.factory = factory;
this.workspace = workspace;
this.eventBroker.Subscribe("ProductSaved", this.WhenProductSaved);
}
public void WhenDataGridRowDoubleClicked(int productId)
{
var editPresenter = this.factory(productId);
var editControl = new EditProductUserControl(editPresenter);
this.workspace.Open(editControl);
}
public void WhenProductSaved(object sender, EventArgs e)
{
// refresh the data grid, etc.
}
}
Así que la ListProductsPresenter
sabe acerca de la función Edit
set (es decir, el presentador de edición y el control de edición de usuario) - y esto está perfectamente bien, van de la mano - pero no necesita conocer todas las dependencias del conjunto de características Edit
, en lugar de confiar en un delegado proporcionado por Autofac para resolver todas esas dependencias.
En general, encuentro que tengo una correspondencia uno-a-uno entre un "presentador/modelo de vista/controlador supervisor" (no nos pongamos demasiado al tanto de las diferencias ya que al final del día todas son bastante similares) y un "UserControl
/Form
". El UserControl
acepta el modelo/controlador del presentador/vista en su constructor y databinds a sí mismo como es apropiado, difiriendo al presentador tanto como sea posible.Algunas personas ocultan el UserControl
del presentador a través de una interfaz, como IEditProductView
, que puede ser útil si la vista no es completamente pasiva. Tiendo a usar el enlace de datos para todo, de modo que la comunicación se realiza a través del INotifyPropertyChanged
y no me moleste.
Pero, hará que su vida sea mucho más fácil si el presentador está descaradamente atada a la vista. ¿Una propiedad en su modelo de objeto no se combina con enlace de datos? Exponer una nueva propiedad así lo hace. Nunca va a tener un EditProductPresenter
y un EditProductUserControl
con un diseño y luego desea escribir una nueva versión del control de usuario que funcione con el mismo presentador. Simplemente los editará a ambos, son para todos los efectos y tienen una función, una función, el presentador solo existe porque es fácilmente comprobable por unidad y el control del usuario no.
Si desea que una característica sea reemplazable, debe abstraer toda la función como tal. Por lo tanto, es posible que tenga una interfaz INavigationFeature
con la que se comunique su MainForm
. Puede tener un TreeBasedNavigationPresenter
que implemente INavigationFeature
y lo consuma un TreeBasedUserControl
. Y es posible que tenga un CarouselBasedNavigationPresenter
que también implemente INavigationFeature
y lo consuma un CarouselBasedUserControl
. Los controles de usuario y los presentadores aún van de la mano, pero su MainForm
no tendría que importar si está interactuando con una vista basada en árbol o en un carrusel, y podría cambiarlos sin que el MainForm
sea el más sabio
Para terminar, es fácil confundirse. Todo el mundo es pedante y utiliza una terminología ligeramente diferente para transmitir diferencias sutiles (y muchas veces poco importantes) entre patrones arquitectónicos similares. En mi humilde opinión, la inyección de dependencia hace maravillas para construir aplicaciones compostables y extensibles, ya que el acoplamiento se mantiene abajo; la separación de características en "presentadores/modelos/controladores de vista" y "vistas/controles/formularios de usuario" hace maravillas por la calidad, ya que la mayoría de la lógica se integra en la primera, lo que permite que sea probada de manera sencilla; y combinar los dos principios parece ser realmente lo que estás buscando, simplemente te confundes con la terminología.
O, podría estar lleno de eso. ¡Buena suerte!
Gracias por la respuesta. No estoy tratando de dejarme absorber por la jerga de la filosofía que puede inspirarme hablando de patrones de diseño, pero estoy tratando de obtener algunos conocimientos técnicos a mis espaldas. Como tal, esta Vista Pasiva realmente me está agravando. ¿Cómo puedo crear, desde el principio, una vista maestra con subvistas dentro de ella (de modo que toda la vista maestra sea intercambiable, así como cualquiera de las subvistas dentro de la vista maestra)? No puedo encontrar nada en línea para abordar este problema de manera adecuada (tal vez nadie lo haya resuelto definitivamente todavía). –
Esta respuesta tiene más de 6 años y, sin embargo, es una de las mejores explicaciones del uso práctico de MVP que he encontrado en Internet. Gracias, es increíblemente educativo, incluso si la programación de WinForms se volvió algo obsoleta hoy en día. – Tarec