13

Ich baue eine WinForms-Anwendung mit einer UI, die nur aus NotifyIcon und ihrer dynamisch bestückten ContextMenuStrip besteht. Es gibt eine MainForm, um die Anwendung zusammen zu halten, aber das ist nie sichtbar.IoC: Abhängigkeiten von Event-Handlern verschalten

Ich machte mich daran, dies so solide wie möglich zu bauen (mit Autofac, um die Objektgrafik zu handhaben) und bin ziemlich zufrieden mit meinem Erfolg, meist auch ziemlich gut mit dem O-Teil. Mit der Erweiterung, die ich gerade implementiere, scheint es, dass ich einen Fehler in meinem Design entdeckt habe und etwas umbauen muss; Ich denke, ich weiß, wie ich gehen muss, aber ich weiß nicht genau, wie ich die Abhängigkeiten genau definieren soll.

Wie oben erwähnt, wird das Menü nach dem Start der Anwendung zum Teil dynamisch ausgefüllt. Zu diesem Zweck definierte I eine IToolStripPopulator Schnittstelle:

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

Eine Implementierung dieses in die MainForm eingespritzt wird, und die Methode aufruft Load()PopulateToolStrip() mit den ContextMenuStrip und einem Handlers in der Form definiert ist. Die Abhängigkeiten des Populators beziehen sich nur auf das Abrufen der Daten, die für die Menüelemente verwendet werden sollen.

Diese Abstraktion hat gut durch ein paar evolutionäre Schritte gearbeitet, aber ist nicht mehr ausreichend, wenn ich mehr als einen Event-Handler, z. weil ich mehrere verschiedene Gruppen von Menüpunkten erstelle - immer noch versteckt hinter einer einzigen IToolStripPopulator Schnittstelle, weil das Formular damit überhaupt nichts zu tun haben sollte.

Wie gesagt, ich glaube, ich weiß, was die allgemeine Struktur sein sollte - ich die IToolStripPopulator Schnittstelle zu etwas spezielleren * umbenannt und eine neue, dessen PopulateToolStrip() Methode nicht nehmen einen EventHandler Parameter, die stattdessen in injiziert wird das Objekt (was auch viel mehr Flexibilität bezüglich der Anzahl von Handlern ermöglicht, die von einer Implementierung usw. benötigt werden). Auf diese Weise kann mein "vorderster" IToolStripPopulator sehr einfach ein Adapter für eine beliebige Anzahl von spezifischen sein.

Nun, was ich unklar bin, ist die Art, wie ich die EventHandler-Abhängigkeiten auflösen sollte. Ich denke, die Handler sollten alle im MainForm definiert werden, weil das alle anderen Abhängigkeiten hat, die benötigt werden, um auf die Menüereignisse richtig zu reagieren, und es "besitzt" auch das Menü. Das würde bedeuten, dass meine Abhängigkeiten für IToolStripPopulator Objekte, die schließlich in das MainForm-Objekt injiziert werden, Abhängigkeiten vom MainForm-Objekt selbst unter Verwendung von Lazy<T> benötigen.

Mein erster Gedanke war die Definition einer IClickHandlerSource Schnittstelle:

public interface IClickHandlerSource 
{ 
    EventHandler GetClickHandler(); 
} 

Dieses von meiner MainForm umgesetzt wurde, und meine spezifischen IToolStripPopulator Implementierung nahm eine Abhängigkeit von Lazy<IClickHandlerSource>. Während das funktioniert, ist es unflexibel. Ich würde entweder separate Schnittstellen für eine potentiell wachsende Anzahl von Handlern definieren müssen (die OCP mit der MainForm Klasse schwer verletzen) oder IClickHandlerSource kontinuierlich erweitern (hauptsächlich verletzend ISP). Direkt Abhängigkeiten von den Event-Handlern zu nehmen, scheint auf der Konsumentenseite eine nette Idee zu sein, aber die Konstrukteure über Eigenschaften von Lazy-Instance (oder ähnlichem) individuell zu verdrahten, scheint ziemlich chaotisch - wenn überhaupt möglich.

Meine beste Wette scheint derzeit dabei, um:

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

Die Schnittstelle nach wie vor von MainForm und injizierte als faul Singleton implementiert werden würde, und EventHandlerType eine benutzerdefinierte Enum mit den verschiedenen Arten sein würden, was ich brauche. Dies wäre immer noch nicht sehr OCP-konform, aber einigermaßen flexibel. EventHandlerType würde natürlich für jeden neuen Typ von Event-Handler eine Änderung haben, ebenso wie die Auflösungslogik in MainForm, zusätzlich zu dem neuen Event-Handler selbst und der (wahrscheinlich) neu geschriebenen zusätzlichen Implementierung von IToolStripPopulator.

Oder .... eine eigene Implementierung von IEventHandlerSource, die (als einziges Objekt) auf Lazy<MainForm> eine Abhängigkeit nimmt und löst die EventHandlerType Optionen auf die spezifischen Handler in MainForm definiert?

Ich versuche, eine Möglichkeit zu erwägen, die Event-Handler aus MainForm in einer realisierbaren Weise zu bekommen, aber kann jetzt nicht ganz scheinen.

Was ist meine beste Option hier, bietet die lockerste Kopplung und eleganteste Auflösung der verschiedenen Event-Handler?

[* Ja, ich wahrscheinlich den Namen haben sollte allein gelassen mit OCP, um wirklich zu erfüllen, aber es sah so besser.]

+2

ich Ihre Frage mehrmals gelesen, aber ich einige Informationen fehlen. Kannst du mehr Code zeigen? Wie sehen beispielsweise die Verbraucher von "IEventHandlerSource" aus und wie sieht die Implementierung von "IEventHandlerSource" aus, und wie wird dies alles registriert? – Steven

Antwort

0

Wenn Sie untergeordnete Komponenten in Ihrer Form hosten, und sie können die Hauptbevöl Anwendung "Shell".

Sie könnten eine Basisklasse ShellComponent haben, die von System.Windows.Forms.ContainerControl erbt. Dies wird Ihnen auch eine Designoberfläche geben. Anstatt IToolStripPopulator zu verwenden, könnten Sie ITool wie solche haben.

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

In ShellComponent könnten Sie ist public List<ITool> OnAddTools(ToolStrip toolStrip) von der Mainform jedes Mal eine Ansicht geladen nennen. Auf diese Weise wäre die Komponente für das Auffüllen des Toolstrips zuständig. Die ShellComponent fragt dann den IoC-Container nach Handlern ab, die ITool<T> implementieren. Auf diese Weise Ihre ITool<T> bietet eine Möglichkeit, das Ereignis zu trennen (in MainForm oder ContainerControl) und schieben Sie es in jede Klasse. Sie können auch genau definieren, was Sie für Argumente übergeben möchten (im Gegensatz zu MouseClickEventArgs ect).

2

Was ist meine beste Option hier, bietet die lockerste Kopplung und die meisten elegante Auflösung der verschiedenen Event-Handler?

Gemeinsame Lösung gibt es nicht und es hängt von der globalen Anwendungsarchitektur ab.

Wenn Sie eine lockersten Kopplung möchten, können EventAggregator Muster, das Sie in einem solchen Fall (Ihre IEventHandlerSource ähnlich der) helfen:

Aber globale Ereignisse sollten mit große Vorsicht verwendet werden - sie können schmieren Architektur, weil die Veranstaltung abonnieren wird überall möglich sein.

Wichtig in DI und IoC: niedrigere Abhängigkeit sollte nicht über höhere Abhängigkeit wissen. Ich denke, und wie Leon zuvor sagte, wird es besser sein, einige Schnittstelle wie ITool<T> erstellen und Liste der Tools in der MainForm zu speichern. Nach einer Aktion ruft MainForm bestimmte Methoden in diesem Tool auf.

1

Erstens, ich denke, Sie sollten nicht behaupten, dass Ihr Hauptformular alle Event-Handler enthalten muss. Sie sind klar auf die Tatsache gestoßen, dass es unterschiedliche Handler mit unterschiedlichen Bedürfnissen gibt (und wahrscheinlich auch unterschiedliche Abhängigkeiten), warum sollten sie alle in dieselbe Klasse eingeordnet werden? Sie können sich wahrscheinlich von der Art und Weise inspirieren lassen, wie Ereignisse in anderen Sprachen gehandhabt werden. WPF verwendet Befehle, Java hat Listener. Beide sind Objekte, nicht nur ein Delegierter, wodurch sie in einem IOC-Szenario leichter zu handhaben sind. Es ist ziemlich einfach, so etwas zu simulieren. Sie könnten das Tag auf Ihren Symbolleistenelementen wie folgt verwenden: Binding to commands in WinForms oder verwenden Sie Lambda-Ausdrücke in PopulateToolbar (siehe Is there anything wrong with using lambda for winforms event?), um das Symbolleistenelement mit dem richtigen Befehl zu verknüpfen. Da PopulateToolbar weiß, welche Elemente erstellt werden müssen, weiß es auch, welche Aktion/welcher Befehl zu jedem Element gehört.

Für das Objekt, das die Aktion darstellt, können unabhängig von der Hauptform oder anderen Aktionen eigene Abhängigkeiten eingefügt werden. Symbolleistenelemente mit ihren eigenen Aktionen können später hinzugefügt oder entfernt werden, ohne dass sich dies auf das Hauptformular oder eine der anderen Aktionen auswirkt. Jede Aktion kann unabhängig getestet und umstrukturiert werden.

Unterm Strich, hör auf, über EventHandler nachzudenken, fang an, über Aktionen/Befehle als eigenständige Einheit nachzudenken und es wird einfacher, ein passendes Muster zu finden. Stellen Sie sicher, dass Sie die Command Pattern verstehen, denn das ist ziemlich genau das, was Sie hier brauchen.

1

Haben Sie versucht, einen Ereignisaggregator zu verwenden? Siehe: Caliburn framework, event Aggregator Ein Ereignis Aggregator wird den Toolstrip von Ihrem Hauptformular entkoppeln.

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

Wrap der Aggregator für Bequemlichkeit wie folgt aus:

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

Sie definieren einen oder mehrere Ereignisklassen, zum Beispiel:

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

In der Controller, der die Toolstrip schafft, veröffentlichen die benutzerdefiniertes Ereignis im Click-Handler schreiben:

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

In der Mainform implementieren die Schnittstelle iHandle

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