13

sto costruendo un'applicazione WinForms con un'interfaccia utente che consiste solo di un NotifyIcon e la sua dinamica popolato ContextMenuStrip. C'è un MainForm per tenere insieme l'applicazione, ma non è mai visibile.CIO: Cablaggio dipendenze da gestori di eventi

Ho deciso di costruire questo il più SOLAMENTE possibile (utilizzando Autofac per gestire il grafico dell'oggetto) e sono piuttosto soddisfatto del mio successo, soprattutto andando molto d'accordo anche con la parte O. Con l'estensione che sto attualmente implementando sembra che ho scoperto un difetto nel mio design e ho bisogno di rimodellarlo un po '; Penso che conosca il modo in cui devo andare, ma sono un po 'oscuro su come definire esattamente le dipendenze.

Come accennato in precedenza, il menu viene compilato in parte in modo dinamico dopo l'avvio dell'applicazione. A questo scopo, ho definito un IToolStripPopulator interfaccia:

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

Un'implementazione di questo viene iniettato nel MainForm, e il metodo Load() chiama PopulateToolStrip() con il ContextMenuStrip e un gestore definito nella forma. Le dipendenze del populatore sono solo legate all'ottenimento dei dati da utilizzare per le voci di menu.

Questa astrazione ha funzionato bene attraverso alcuni passaggi evolutivi ma non è più sufficiente quando ho bisogno di più di un gestore di eventi, ad es. perché sto creando diversi gruppi di voci di menu - ancora nascosti dietro una singola interfaccia IToolStripPopulator perché il modulo non dovrebbe preoccuparsi affatto di quello.

Come ho detto, credo di sapere quale sia la struttura generale dovrebbe essere come - ho rinominato l'interfaccia IToolStripPopulator a qualcosa di più specifico * e ha creato uno nuovo la cui PopulateToolStrip() metodo non prende un parametro EventHandler, che viene invece iniettato l'oggetto (che consente anche molta più flessibilità per quanto riguarda il numero di gestori richiesti da un'implementazione, ecc.). In questo modo il mio "primo" IToolStripPopulator può essere facilmente un adattatore per un numero qualsiasi di quelli specifici.

Ora quello che non sono chiaro è il modo in cui dovrei risolvere le dipendenze di EventHandler. Penso che i gestori dovrebbero essere tutti definiti nel MainForm, perché questo ha tutte le altre dipendenze necessarie per reagire correttamente agli eventi del menu, e anche "possiede" il menu. Ciò significherebbe che le mie dipendenze per gli oggetti IToolStripPopulator eventualmente iniettati nel MainForm avrebbero bisogno di assumere dipendenze sull'oggetto MainForm utilizzando Lazy<T>.

Il mio primo pensiero è stato la definizione di un IClickHandlerSource interfaccia:

public interface IClickHandlerSource 
{ 
    EventHandler GetClickHandler(); 
} 

Questo è stato realizzato dal mio MainForm, e la mia specifica IToolStripPopulator implementazione ha una dipendenza da Lazy<IClickHandlerSource>. Mentre questo funziona, è inflessibile. Dovrei definire interfacce separate per un numero potenzialmente crescente di gestori (violando gravemente l'OCP con la classe MainForm) o estendere continuamente IClickHandlerSource (in particolare violando ISP). Le dipendenze dirette dei gestori di eventi sembrano una buona idea da parte dei consumatori, ma il cablaggio individuale dei costruttori tramite proprietà di istanza lazy (o simili) sembra piuttosto complicato, se possibile.

La mia scommessa migliore attualmente sembra essere questo:

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

L'interfaccia sarebbe ancora essere implementata da MainForm e iniettata come Singleton pigro, e EventHandlerType sarebbe un enum personalizzato con i diversi tipi di cui ho bisogno. Ciò non sarebbe ancora molto conforme agli OCP, ma ragionevolmente flessibile. EventHandlerType avrebbe ovviamente una modifica per ogni nuovo tipo di gestore di eventi, come pure la logica di risoluzione in MainForm, oltre al nuovo gestore di eventi stesso e all'implementazione aggiuntiva (probabilmente) di nuova scrittura di IToolStripPopulator.

O .... un'implementazione separata di IEventHandlerSource che (come l'unico oggetto) batte una dipendenza da Lazy<MainForm> e risolve i EventHandlerType opzioni ai gestori specifici definiti in MainForm?

Sto provando a pensare a un modo per ottenere effettivamente i gestori di eventi da MainForm in modo fattibile, ma non riesco a sembrare al momento.

Qual è la mia opzione migliore qui, fornendo l'accoppiamento più flessibile e la più elegante risoluzione dei diversi gestori di eventi?

[* Sì, probabilmente dovuto lasciare il solo nome di rispettare davvero con OCP, ma sembrava meglio così.]

+2

ho letto la tua domanda più volte, ma mi manca un po 'di informazioni. Puoi mostrare più codice? Ad esempio, come appaiono i consumatori di "IEventHandlerSource" e come appare l'implementazione di "IEventHandlerSource" e in che modo tutto questo è stato registrato? – Steven

risposta

0

Se si ospitano componenti figlio all'interno del vostro modulo, e loro possono popolare il principale applicazione "shell".

È possibile avere una classe base ShellComponent, che eredita da System.Windows.Forms.ContainerControl. Questo ti darà anche una superficie di design. Invece di affidarsi a IToolStripPopulator, si potrebbe avere ITool come tale.

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

In ShellComponent si potrebbe chiamare, public List<ITool> OnAddTools(ToolStrip toolStrip) dal MainForm ogni volta che una vista è caricato. In questo modo il componente sarebbe responsabile della compilazione della traccia di strumenti.

Il "ShellComponent" chiedeva quindi al contenitore IoC i gestori che implementano ITool<T>. In questo modo, il tuo ITool<T> fornisce un modo per separare l'evento (nello MainForm o nello ContainerControl) e inviarlo a qualsiasi classe. Consente inoltre di definire esattamente ciò che si desidera passare per argomenti (al contrario di MouseClickEventArgs ect).

2

Qual è la mia opzione migliore qui, fornendo l'accoppiamento più flessibile e la maggior parte della risoluzione elegante dei diversi gestori di eventi?

La soluzione comune non esiste e dipende dall'architettura dell'applicazione globale.

Se si desidera un accoppiamento loosest, EventAggregator modello può aiutare in tal caso (il vostro IEventHandlerSource simile a quello):

Tuttavia, gli eventi globali devono essere utilizzati con grande attenzione - possono macchia architettura, perché iscriversi all'evento sarà possibile ovunque.

Importante in DI e IoC: una dipendenza più bassa non dovrebbe sapere di una maggiore dipendenza. Penso, e come Leon ha detto prima, sarà meglio creare un'interfaccia come ITool<T> e memorizzare l'elenco degli strumenti nel MainForm. Dopo alcune azioni, MainForm invocherà determinati metodi in questi strumenti.

1

In primo luogo, penso che non si debba richiedere che la propria scheda principale contenga tutti i gestori di eventi. È chiaro che ci sono diversi gestori con esigenze diverse (e probabilmente diverse dipendenze), quindi perché dovrebbero essere tutti inseriti nella stessa classe? Probabilmente puoi trarre ispirazione dal modo in cui gli eventi vengono gestiti in altre lingue. WPF utilizza i comandi, Java ha Listeners. Entrambi sono oggetti, non solo un delegato, rendendoli più facili da gestire in uno scenario CIO. È abbastanza facile simulare qualcosa del genere. È possibile abusare del tag sugli elementi della barra degli strumenti, ad esempio: Binding to commands in WinForms o utilizzare espressioni lambda all'interno di PopulateToolbar (vedere Is there anything wrong with using lambda for winforms event?) per associare l'elemento della barra degli strumenti con il comando corretto. Ciò presuppone che dal momento che PopulateToolbar sa quali oggetti devono essere creati, sa anche quale azione/comando appartiene a ciascun elemento.

L'oggetto che rappresenta l'azione può avere le proprie dipendenze iniettate, indipendentemente dalla forma principale o da altre azioni. Gli elementi della barra degli strumenti con le proprie azioni possono quindi essere aggiunti o rimossi in seguito senza influire sul modulo principale o su altre azioni, ciascuna azione può essere verificata e sottoposta a refactoring in modo indipendente.

Bottom line, smettere di pensare a EventHandlers, iniziare a pensare a Azioni/Comandi come un'entità a sé stante e sarà più facile trovare un modello adatto. Assicurati di aver compreso lo Command Pattern, perché è praticamente ciò di cui hai bisogno qui.

1

Hai provato a utilizzare un aggregatore di eventi? Vedere: Caliburn framework, event Aggregator Un evento aggregatore disaccoppiare il toolstrip da voi form principale.

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

Avvolgere l'aggregatore per comodità in questo modo:

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

È definire una o più classi di eventi, ad esempio:

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

Nel regolatore che crea il ToolStrip, pubblicare il evento personalizzato nel gestore Click scrivere:

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

Nel mainForm implementare l'interfaccia IHandle

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