13

NotifyIconとその動的に配置されたContextMenuStripで構成されるUIでWinFormsアプリケーションを構築しています。アプリケーションをまとめて保持するためのMainFormがありますが、これは決して見えません。IoC:イベントハンドラへの依存関係の配線

私はこれをできるだけ完全に(オブジェクトグラフを扱うためにAutofacを使用して)構築することに着手しました。そして、私の成功に大変満足しています。私が現在実装している拡張機能では、私のデザインに欠陥があり、少し改造する必要があるようです。私は私が行く必要がある方法を知っていると思いますが、依存関係を正確に定義する方法については少し不明です。

前述のとおり、メニューはアプリケーションの起動後に部分的に動的に取り込まれます。この目的のために、私はIToolStripPopulatorインターフェイスを定義:

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

これの実装は、MainFormに注入され、そしてLoad()方法は、ContextMenuStripと形で定義されたハンドラにPopulateToolStrip()を呼び出します。ポピュレータの依存関係は、メニュー項目に使用するデータの取得にのみ関連しています。

この抽象化は、いくつかの進化的なステップでうまく機能しましたが、複数のイベントハンドラが必要な場合には、これ以上十分ではありません。私はいくつかの異なるメニュー項目のグループを作成しています.1つのIToolStripPopulatorインターフェースの後ろに隠れていても、フォームはそれにはまったく関わってはいけないからです。私はより具体的なものにIToolStripPopulatorインタフェース*に改名し、代わりに注入され、そのPopulateToolStrip()方法EventHandlerパラメータをとらない新しいものを、作成した - 私が言ったように

は、私はのような一般的な構造がどうあるべきか知っていると思いますオブジェクト(実装などで必要とされるハンドラの数に関してはるかに柔軟性を持たせることも可能です)。このように私の "最前の" IToolStripPopulatorは非常に簡単に任意の数のアダプターになります。

ここで私が不明な点は、EventHandlerの依存関係を解決する方法です。私はハンドラがMainFormで定義されるべきだと考えます。なぜなら、それはメニューイベントに適切に反応するために必要な他のすべての依存関係を持ち、メニューも "所有"しているからです。つまり、IToolStripPopulatorオブジェクトの依存関係が最終的にMainFormに注入されると、Lazy<T>を使用してMainFormオブジェクト自体に依存する必要があります。

public interface IClickHandlerSource 
{ 
    EventHandler GetClickHandler(); 
} 

これは私のMainFormによって実装されました、そして、私の具体的なIToolStripPopulator実装はLazy<IClickHandlerSource>への依存を取っ:

私が最初に考えたのはIClickHandlerSourceインタフェースを定義されました。これは機能しますが、柔軟性がありません。 (MainFormクラスのOCPに深刻に違反する)ハンドラの数が増えるにつれて別のインターフェイスを定義するか、またはIClickHandlerSource(主に違反しているISP)を継続的に拡張する必要があります。 イベントハンドラに直接依存することは、コンシューマ側の良いアイデアのように見えますが、遅延インスタンス(または同様のもの)のプロパティを使用してコンストラクタを個々に配線することは可能です。

私の最善の策は、現在、このように思わ:

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

インタフェースはまだMainFormによって実装され、怠惰なシングルトンとして注入されるだろう、とEventHandlerTypeは私が必要とするさまざまな種類のカスタム列挙型になります。これは依然として非常にOCPに準拠しているわけではなく、合理的に柔軟性があります。 EventHandlerTypeは、明らかに新しいイベントハンドラの種類と、新しいイベントハンドラ自体と(おそらく)新たに書かれた追加実装IToolStripPopulatorに加えて、解決ロジックがMainFormにあるように、新しいイベントハンドラごとに変更を加えることになります。

あるいは...(唯一のオブジェクトとして)MainFormで定義された特定のハンドラにEventHandlerTypeオプションをLazy<MainForm>依存を取り、解決IEventHandlerSourceの別の実装?

実際にイベントハンドラをMainFormの外に出す方法を考えようとしていますが、現時点ではあまり見えません。

他のイベントハンドラの最も緩やかなカップリングと最もエレガントな解決策を提供して、私の最高のオプションは何ですか?

[*はい、私はおそらく本当にOCPに準拠するために、単独で名を残しているはずですが、それは、より良い、そのように見えた。]

+2

私はあなたの質問を複数回読みを実装するが、私はいくつかの情報が欠けています。より多くのコードを表示できますか?たとえば、 'IEventHandlerSource'のコンシューマはどのように見えますか?' IEventHandlerSource'の実装はどのように見えますか?これはどのように登録されていますか? – Steven

答えて

0

あなたは、フォーム内の子コンポーネントをホストしている場合、彼らはメインを移入することができますアプリケーション "シェル"。

System.Windows.Forms.ContainerControlから継承した基本クラスShellComponentがあります。これにより、デザイン面も提供されます。 IToolStripPopulatorに頼る代わりに、IToolをそのようにすることができます。 ShellComponent

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

あなたはMainFormをからpublic List<ITool> OnAddTools(ToolStrip toolStrip)、ビューがロードされるたびに呼び出すことができます。このようにして、コンポーネントはツールストリップを投入する役割を担います。

'ShellComponent'は、IoCコンテナにITool<T>を実装するハンドラを要求します。このようにしてITool<T>は、イベントを(MainFormまたはContainerControlに)分けて、どのクラスにもプッシュする方法を提供します。また、(MouseClickEventArgs ectではなく)引数のために渡すものを正確に定義することもできます。

2

ここで私の最善の選択肢は、最も緩やかなカップリングと、ほとんどの異なるイベントハンドラの のエレガントな解決策を提供していますか?

一般的な解決策は存在せず、グローバルなアプリケーションアーキテクチャに依存します。

あなたは最も緩いカップリングをしたい場合は、EventAggregatorパターンは、このような場合(とあなたのIEventHandlerSource似た)であなたを助けることができる:

グローバルなイベントはと一緒に使用してください。 - 彼らはイベントにサブスクライブすることがどこでも可能になるので、の汚れアーキテクチャを行うことができます。

DIとIoCで重要なこと:依存性が低いほど依存度が高いことを知るべきではありません。 Leonが以前に言ったように、ITool<T>のようなインターフェイスを作成し、ツールのリストをMainFormに保存する方が良いと思います。いくつかのアクションの後にMainFormは、このツールで特定のメソッドを呼び出します。

1

まず、メインフォームにすべてのイベントハンドラを含める必要があると主張するべきではないと思います。異なるニーズ(おそらく異なる依存関係)を持つハンドラがあるという事実にはっきりと遭遇しました。なぜそれらはすべて同じクラスに適合するべきですか? イベントが他の言語で処理される方法からインスピレーションを得ることができます。 WPFはコマンドを使用し、Javaはリスナーを持ちます。両方ともオブジェクトであり、デリゲートだけでなく、IOCシナリオでの対処が容易です。そのようなものをシミュレートするのはかなり簡単です。ツールバー項目のタグを悪用すると、Binding to commands in WinForms、またはPopulateToolbar(Is there anything wrong with using lambda for winforms event?を参照)の中のラムダ式を使用して、ツールバー項目を正しいコマンドに関連付けることができます。これは、PopulateToolbarはどのアイテムを作成する必要があるかを知っているので、各アイテムにどのアクション/コマンドが属しているかも知っていることを前提としています。

アクションを表すオブジェクトには、メインフォームやその他のアクションとは独立して、独自の依存関係を挿入できます。独自のアクションを持つツールバーアイテムは、メインフォームや他のアクションに影響を与えずに後で追加または削除することができ、各アクションを個別にテストしてリファクタリングすることができます。

ボトムラインでは、EventHandlerについて考えるのをやめて、エンティティとしてアクション/コマンドについて考えてみましょう。適切なパターンを思い付くようになります。 Command Patternを理解していることを確認してください。なぜなら、これはあなたがここで必要とするものなのです。

1

イベントアグリゲータを使用しようとしましたか?参照:Caliburn framework, event Aggregator イベントアグリゲータは、ツールストリップをメインフォームから切り離します。

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

ラップこのような利便性のためのアグリゲータ:

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

あなたは、たとえば、1つ以上のイベント・クラスを定義:ツールストリップを作成し、コントローラに

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

、パブリッシュClickハンドラのカスタムイベント書き込み:

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

はインターフェースIHandle

public class MainForm : Form, IHandle<EventToolStripClick> 
{ 
    ... 
    public void Handle(EventToolStripClick evt) 
    { 
     //your implementation here 
    } 
} 
関連する問題