C# IoC:连接事件处理程序上的依赖项
我正在构建一个WinForms应用程序,其UI只包含一个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部件相处得也很好。随着我目前正在实施的扩展,我似乎发现了我的设计中的一个缺陷,需要重新设计一点;我想我知道我需要走的路,但对于如何准确定义依赖关系,我有点不清楚 如上所述,启动应用程序后,部分菜单将动态填充。为
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
}
}