13

Estoy construyendo una aplicación WinForms con una interfaz de usuario que solo consiste en NotifyIcon y su ContextMenuStrip dinámicamente poblado. Hay un MainForm para mantener la aplicación en conjunto, pero eso nunca es visible.IoC: cableando dependencias en controladores de eventos

Me propuse construir esto lo más SOLIDAMENTE posible (usando Autofac para manejar el gráfico de objetos) y estoy bastante satisfecho con mi éxito, la mayoría de las veces me llevo muy bien incluso con la parte O. Con la extensión que estoy implementando, parece que descubrí un defecto en mi diseño y necesito remodelarlo un poco; Creo que sé cómo debo ir, pero no estoy seguro de cómo definir exactamente las dependencias.

Como se mencionó anteriormente, el menú se rellena en parte dinámicamente después de iniciar la aplicación. Para este propósito, he definido una interfaz IToolStripPopulator:

public interface IToolStripPopulator 
{ 
    System.Windows.Forms.ToolStrip PopulateToolStrip(System.Windows.Forms.ToolStrip toolstrip, EventHandler itemclick); 
} 

se inyecta una implementación de este en el MainForm, y el método Load() llamadas PopulateToolStrip() con el ContextMenuStrip y un controlador se define en el formulario. Las dependencias del usuario solo están relacionadas con la obtención de los datos que se utilizarán para los elementos del menú.

Esta abstracción ha funcionado bien mediante algunos pasos evolutivos, pero ya no es suficiente cuando necesito más de un controlador de eventos, p. porque estoy creando varios grupos diferentes de elementos de menú, aún ocultos detrás de una sola interfaz IToolStripPopulator porque el formulario no debería preocuparse por eso en absoluto.

Como ya he dicho, creo que sé lo que la estructura general debe ser como - Me cambió el nombre de la interfaz IToolStripPopulator a algo más específico * y ha creado uno nuevo cuya PopulateToolStrip() método no tiene un parámetro EventHandler, que en cambio se inyecta en el objeto (también permite mucha más flexibilidad con respecto al número de manipuladores requeridos por una implementación, etc.). De esta manera, mi "principal" IToolStripPopulator puede ser fácilmente un adaptador para cualquier cantidad específica.

Ahora, lo que no tengo claro es la forma en que debo resolver las dependencias de EventHandler. Creo que los manipuladores deberían definirse todos en el MainForm, porque eso tiene todas las otras dependencias necesarias para reaccionar adecuadamente a los eventos del menú, y también "posee" el menú. Eso significaría que mis dependencias para objetos IToolStripPopulator eventualmente inyectados en MainForm necesitarían tomar dependencias en el objeto MainForm mismo utilizando Lazy<T>.

Mi primer pensamiento fue definir una interfaz IClickHandlerSource:

public interface IClickHandlerSource 
{ 
    EventHandler GetClickHandler(); 
} 

Esto fue implementado por mi MainForm, y mi aplicación específica IToolStripPopulator tomó una dependencia de Lazy<IClickHandlerSource>. Mientras esto funciona, es inflexible. Tendría que definir interfaces separadas para un número potencialmente creciente de controladores (que violan severamente el OCP con la clase MainForm) o extender de forma continua IClickHandlerSource (que viola principalmente al ISP). Tomar dependencias directas de los manejadores de eventos parece una buena idea para los consumidores, pero el cableado individual de los constructores a través de las propiedades de la instancia perezosa (o similar) parece bastante desordenado, si es posible.

Mi mejor apuesta actualmente parece ser la siguiente:

public interface IEventHandlerSource 
{ 
    EventHandler Get(EventHandlerType type); 
} 

La interfaz todavía sería ejecutado por MainForm y se inyecta como un conjunto unitario perezoso, y EventHandlerType habría una enumeración personalizada con los diferentes tipos que necesito. Esto todavía no sería muy OCP, pero razonablemente flexible. EventHandlerType obviamente tendría un cambio para cada nuevo tipo de controlador de eventos, como lo haría la lógica de resolución en MainForm, además del nuevo controlador de eventos en sí y la (probablemente) nueva implementación adicional escrita de IToolStripPopulator.

O .... una aplicación separada de IEventHandlerSource que (como el único objeto) realiza una dependencia de Lazy<MainForm> y resuelve los EventHandlerType opciones a los gestores específicos definidos en MainForm?

Estoy tratando de pensar en una forma de realmente obtener los controladores de eventos fuera de MainForm de una manera factible, pero no puede parecerlo en este momento.

¿Cuál es mi mejor opción aquí, proporcionando el acoplamiento más flojo y la resolución más elegante de los diferentes manejadores de eventos?

[* Sí, probablemente debería haber dejado el nombre por sí solo para cumplir realmente con OCP, pero se veía mejor de esa manera.]

+2

leí su pregunta varias veces, pero me falta un poco de información. ¿Puedes mostrar más código? Por ejemplo, ¿cómo se ven los consumidores de 'IEventHandlerSource', y cómo se ve la implementación de' IEventHandlerSource', y cómo está todo esto registrado? – Steven

Respuesta

0

Si usted es anfitrión de componentes hijos dentro de su forma, y ​​se puede instalar en la principal aplicación "shell".

Puede tener una clase base ShellComponent, que hereda de System.Windows.Forms.ContainerControl. Esto le dará una superficie de diseño también. En lugar de confiar IToolStripPopulator, podría tener ITool como tal.

public inteface ITool<T> 
{ 
    int ToolIndex { get; } 
    string Category { get; } 
    Action OnClick(T eventArgs); 
} 

En ShellComponent que se podría llamar, public List<ITool> OnAddTools(ToolStrip toolStrip) del MainForm cada vez que se carga una vista. De esta forma, el componente sería responsable de rellenar la tira de herramientas.

El 'ShellComponent' le preguntaría al contenedor IoC a los controladores que implementan ITool<T>. De esta manera, su ITool<T> proporciona una manera de separar el evento (en el MainForm o el ContainerControl) y enviarlo a cualquier clase. También le permite definir exactamente para lo que desea pasar los argumentos (a diferencia de MouseClickEventArgs ect).

2

¿Cuál es mi mejor opción aquí, proporcionando el acoplamiento más flojo y la mayor resolución elegante de los diferentes controladores de eventos?

La solución común no existe y depende de la arquitectura de la aplicación global.

Si quieres un acoplamiento más flojo, EventAggregator patrón puede ayudarle en este caso (el IEventHandlerSource similar a la):

Pero, los eventos mundiales se deben utilizar con gran precaución - pueden manchar arquitectura, porque suscribirse al evento será posible en cualquier lugar.

Lo importante en DI y IoC: una menor dependencia no debería saber sobre una mayor dependencia. Creo, y como Leon dijo anteriormente, será mejor crear alguna interfaz como ITool<T> y la lista de herramientas de la tienda en el MainForm. Después de alguna acción, MainForm invocará ciertos métodos en estas herramientas.

1

En primer lugar, creo que no debe reclamar que su formulario principal debe contener todos los controladores de eventos. Claramente se encontró con el hecho de que hay diferentes manejadores con diferentes necesidades (y probablemente dependencias diferentes), entonces, ¿por qué deberían todos ellos encajar en la misma clase? Probablemente pueda inspirarse en la forma en que se manejan los eventos en otros idiomas. WPF usa Comandos, Java tiene Oyentes. Ambos son objetos, no solo un delegado, lo que los hace más fáciles de manejar en un escenario de IOC. Es bastante fácil simular algo así. Puede abusar de la etiqueta en los elementos de la barra de herramientas, como esta: Binding to commands in WinForms o usar expresiones lambda dentro de PopulateToolbar (consulte Is there anything wrong with using lambda for winforms event?) para asociar el elemento de la barra de herramientas con el comando correcto. Esto supone que, dado que PopulateToolbar sabe qué elementos se deben crear, también se sabe qué acción/comando pertenece a cada elemento.

El objeto que representa la acción puede tener sus propias dependencias inyectadas, independientemente de la forma principal u otras acciones. Los elementos de la barra de herramientas con sus propias acciones se pueden agregar o eliminar más adelante sin afectar su forma principal ni ninguna de las otras acciones; cada acción se puede probar y refactorizar de forma independiente.

En pocas palabras, deje de pensar en EventHandlers, comience a pensar en Actions/Commands como una entidad en sí misma y será más fácil encontrar un patrón adecuado. Asegúrese de comprender el Command Pattern, porque eso es más o menos lo que necesita aquí.

1

¿Ha intentado utilizar un agregador de eventos? Ver: Caliburn framework, event Aggregator Un agregador de eventos desacoplará la tira de herramientas de su formulario principal.

public interface IToolStripPopulator 
{ 
    ToolStrip PopulateToolStrip(ToolStrip toolstrip); 
} 

Envolver el agregador para la conveniencia de esta manera:

public static class Event 
{ 
    private static readonly IEventAggregator aggregator = new EventAggregator(); 
    public static IEventAggregator Aggregator 
    { 
     get 
     { 
      return aggregator; 
     } 
    } 
} 

Usted definir una o más clases de eventos, por ejemplo:

public class EventToolStripClick { 
    public object Sender {get;set;} 
    public EventArgs Args {get;set;} 
} 

En el controlador que crea la toolstrip, publicar la evento personalizado en el controlador de clic escriba:

public void ControllerToolStripClick(object sender, EventArgs args) 
{ 
    Event.Aggregator.Publish(new EventToolStripClick(){Sender=sender,Args=args)}); 
} 

En el mainForm implementar la interfaz iHandle

public class MainForm : Form, IHandle<EventToolStripClick> 
{ 
    ... 
    public void Handle(EventToolStripClick evt) 
    { 
     //your implementation here 
    } 
} 
Cuestiones relacionadas