C# IoC:连接事件处理程序上的依赖项

C# IoC:连接事件处理程序上的依赖项,c#,winforms,dependency-injection,inversion-of-control,solid-principles,C#,Winforms,Dependency Injection,Inversion Of Control,Solid Principles,我正在构建一个WinForms应用程序,其UI只包含一个NotifyIcon及其动态填充的ContextMenuStrip。有一个MainForm将应用程序保存在一起,但它永远不可见 我开始尽可能牢固地构建它(使用Autofac来处理对象图),并对我的成功感到非常满意,大部分情况下甚至与O部件相处得也很好。随着我目前正在实施的扩展,我似乎发现了我的设计中的一个缺陷,需要重新设计一点;我想我知道我需要走的路,但对于如何准确定义依赖关系,我有点不清楚 如上所述,启动应用程序后,部分菜单将动态填充。为

我正在构建一个WinForms应用程序,其UI只包含一个
NotifyIcon
及其动态填充的
ContextMenuStrip
。有一个
MainForm
将应用程序保存在一起,但它永远不可见

我开始尽可能牢固地构建它(使用Autofac来处理对象图),并对我的成功感到非常满意,大部分情况下甚至与O部件相处得也很好。随着我目前正在实施的扩展,我似乎发现了我的设计中的一个缺陷,需要重新设计一点;我想我知道我需要走的路,但对于如何准确定义依赖关系,我有点不清楚

如上所述,启动应用程序后,部分菜单将动态填充。为此,我定义了一个
IToolStripPopulator
接口:

public interface IToolStripPopulator
{
    System.Windows.Forms.ToolStrip PopulateToolStrip(System.Windows.Forms.ToolStrip toolstrip, EventHandler itemclick);
}
public interface IClickHandlerSource
{
    EventHandler GetClickHandler();
}
这一实现被注入到
MainForm
,并且
Load()
方法使用
ContextMenuStrip
和表单中定义的处理程序调用
PopulateToolStrip()
。填充器的依赖关系仅与获取用于菜单项的数据相关

这种抽象通过几个进化步骤很好地发挥了作用,但当我需要多个事件处理程序时就不再足够了,例如,因为我正在创建多个不同的菜单项组-仍然隐藏在一个
IToolStripPopulator
接口后面,因为表单根本不应该关心这些

正如我所说的,我想我知道一般结构应该是什么样的-我将
IToolStripPopulator
接口重命名为更具体的接口*并创建了一个新接口,它的
PopulateToolStrip()
方法不采用
EventHandler
参数,而是将该参数注入到对象中(还允许在实现所需的处理程序数量等方面具有更大的灵活性)。这样,我的“最重要的”
IToolStripPopulator
就可以很容易地成为任意数量特定处理程序的适配器

现在我不清楚的是我应该如何解决EventHandler依赖关系。我认为所有的处理程序都应该在
MainForm
中定义,因为它具有正确响应菜单事件所需的所有其他依赖关系,并且它还“拥有”菜单。这意味着最终注入MainForm的
IToolStripPopulator
对象的依赖关系需要使用
Lazy
MainForm
对象本身进行依赖

我的第一个想法是定义一个
IClickHandlerSource
接口:

public interface IToolStripPopulator
{
    System.Windows.Forms.ToolStrip PopulateToolStrip(System.Windows.Forms.ToolStrip toolstrip, EventHandler itemclick);
}
public interface IClickHandlerSource
{
    EventHandler GetClickHandler();
}
这是由我的
MainForm
实现的,我的特定
IToolStripPopulator
实现依赖于
Lazy
。虽然这是可行的,但它是不灵活的。我要么必须为数量可能不断增加的处理程序定义单独的接口(严重违反
MainForm
类的OCP)或持续扩展IClickHandlerSource(主要违反ISP)。 从消费者的角度来看,直接依赖事件处理程序看起来是个不错的主意,但通过惰性实例(或类似实例)的属性单独连接构造函数似乎相当混乱——如果可能的话

我目前的最佳选择似乎是:

public interface IEventHandlerSource
{
    EventHandler Get(EventHandlerType type);
}
接口仍将由
MainForm
实现,并作为惰性单例注入,
EventHandlerType
将是一个具有我所需不同类型的自定义枚举。这仍然不是非常符合OCP,但相当灵活。
EventHandlerType
显然会对每种新类型的事件进行更改处理程序,就像
MainForm
中的解析逻辑一样,除了新的事件处理程序本身和(可能)新编写的
IToolStripPopulator
的附加实现之外

或者…一个独立的
IEventHandlerSource
实现,它(作为唯一的对象)依赖于
Lazy
,并将
EventHandlerType
选项解析为
MainForm
中定义的特定处理程序

我正试图想出一种切实可行的方法,将事件处理程序从
MainForm
中取出,但目前似乎还不太可能

这里我最好的选择是什么,为不同的事件处理程序提供最松散的耦合和最优雅的解决方案


[*是的,为了真正遵守OCP,我可能应该不使用名称,但这样看起来更好。]

如果您在表单中托管子组件,并且它们可以填充主应用程序“shell”

您可以有一个基类
ShellComponent
,它继承自System.Windows.Forms.ContainerControl。这也将为您提供一个设计图面。您可以使用类似的IToolStripPopulator,而不是依赖IToolStripPopulator

public inteface ITool<T>
{
   int ToolIndex { get; }
   string Category { get; }
   Action OnClick(T eventArgs);
}
公共接口ITool
{
int工具索引{get;}
字符串类别{get;}
点击操作(T事件参数);
}
ShellComponent
中,您可以在每次加载视图时从主窗体调用,
公共列表OnAddTools(ToolStrip ToolStrip)
。这样,组件将负责填充ToolStrip

然后,“ShellComponent”将向IoC容器请求实现
ITool
的处理程序。这样,您的
ITool
提供了一种分离事件的方法(在
main表单中或
ContainerControl
)它还允许您准确地定义要传递给参数的内容(与mouseClickevenTargets等相反)

我在这里的最佳选择是什么,提供最松散的耦合和最稳定的性能 不同事件处理程序的优雅解析

通用解决方案不存在,它取决于全局应用程序体系结构

如果你想要一个最宽松的公司
public class EventToolStripClick {
    public object Sender {get;set;}
    public EventArgs Args {get;set;}   
}
public void ControllerToolStripClick(object sender, EventArgs args )
{
    Event.Aggregator.Publish(new EventToolStripClick(){Sender=sender,Args=args)});
}
public class MainForm : Form, IHandle<EventToolStripClick>
{
    ...
    public void Handle(EventToolStripClick evt)
    {
        //your implementation here
    }
}