表单事件进入应用程序的正确方式
本文关键字:方式 应用程序 事件 表单 | 更新日期: 2023-09-27 18:12:11
我有一些调试函数,我想重构,但看到他们是调试函数,似乎他们不太可能遵循适当的设计。他们几乎会深入到应用程序的深处,把事情搞砸。
我的应用程序的主表单有一个包含调试函数的菜单,我在表单代码中捕获事件。目前,这些方法请求应用程序中的特定对象,如果它不是null,然后对它进行处理。我正在尝试重构,以便我可以在任何地方删除对该对象的引用,并使用它的接口代替(该接口由许多与调试功能无关的其他对象共享)
作为一个简化的例子,假设我有这样的逻辑代码:public class Logic
{
public SpecificState SpecificState { get; private set; }
public IGenericState GenericState { get; private set; }
}
这个表单代码:
private void DebugMethod_Click(object sender, EventArgs e)
{
if (myLogic.SpecificState != null)
{
myLogic.SpecificState.MessWithStuff();
}
}
所以我试图去掉SpecificState
参考。它已经从应用程序的其他地方根除,但我想不出如何重写调试功能。他们是否应该将实现转移到Logic
类中?如果是,那又怎样呢?将许多MessWithStuff
方法放入IGenericState
中是完全浪费的,因为其他类都将具有空的实现。
编辑
在应用程序的生命周期中,许多IGenericState
实例来来去去。这是一种DFA/策略模式。但是只有一个实现具有调试功能。
旁白:在此上下文中是否有另一个术语表示"调试",指的是仅测试的特性?"Debug"通常只是指修复问题的过程,所以很难搜索到这个东西。
创建一个单独的接口来保存调试函数,例如:
public interface IDebugState
{
void ToggleDebugMode(bool enabled); // Or whatever your debug can do
}
然后你有两个选择,你可以像注入IGenericState
一样注入IDebugState
,如:
public class Logic
{
public IGenericState GenericState { get; private set; }
public IDebugState DebugState { get; private set; }
}
或者,如果您正在寻找更快的解决方案,您可以简单地在对调试敏感的方法中进行接口测试:
private void DebugMethod_Click(object sender, EventArgs e)
{
var debugState = myLogic.GenericState as IDebugState;
if (debugState != null)
debugState.ToggleDebugMode(true);
}
这很好地符合DI原则,因为您实际上并没有在这里创建任何依赖项,只是测试一下您是否已经有了一个依赖项-并且您仍然依赖于抽象而不是具体。
在内部,当然,您仍然有您的SpecificState
实现IGenericState
和IDebugState
,所以只有一个实例 -但这取决于您的IoC容器,您的任何依赖类都不需要知道它。
我强烈推荐您阅读Ninject的依赖注入指南(请务必通读整个教程)。考虑到你的问题,我知道这似乎是一个奇怪的建议;但是,我认为从长远来看,这将为您节省大量时间,并使您的代码更干净。
你的调试代码似乎依赖于SpecificState
;因此,我希望您的调试菜单项会向DI容器询问它们的依赖项,或者可以返回依赖项或null
的提供程序。如果您已经在进行重构以包含DI,那么为您的调试菜单项提供应用程序的适当内部部分作为依赖项(通过DI容器)似乎是在不破坏可靠设计原则的情况下实现这一目标的适当方法。例如:
public sealed class DebugMenuItem : ToolStripMenuItem
{
private SpecificStateProvider _prov;
public DebugMenuItem(SpecificStateProvider prov) : base("Debug Item")
{
_prov = prov;
}
// other stuff here
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
SpecificState state = _prov.GetState();
if(state != null)
state.MessWithStuff();
}
}
这假设SpecificState
的实例并不总是可用的,并且需要通过一个可能返回null的提供者来访问。顺便说一下,这种技术的额外好处是可以减少表单中的事件处理程序。
我倾向于在相对较大的应用中使用依赖注入和装饰器,正如FMM所建议的,但对于较小的应用,你可以对现有代码进行相对简单的扩展。
我假设你以某种方式将Logic
的实例推到应用程序的各个部分-要么通过static
类或字段,要么通过传递给构造函数。
我将用这个接口扩展Logic
:
public interface ILogicDebugger
{
IDisposable PublishDebugger<T>(T debugger);
T GetFirstOrDefaultDebugger<T>();
IEnumerable<T> GetAllDebuggers<T>();
void CallDebuggers<T>(Action<T> call);
}
然后在你的代码深处一些你想要调试的类会调用这段代码:
var subscription =
logic.PublishDebugger(new MessWithStuffHere(/* with params */));
现在在顶层代码中你可以这样调用:
var debugger = logic.GetFirstOrDefaultDebugger<MessWithStuffHere>();
if (debugger != null)
{
debugger.Execute();
}
在调试类上调用方法的一种更短的方法是像这样使用CallDebuggers
:
logic.CallDebuggers<MessWithStuffHere>(x => x.Execute());
回到你的代码深处,当你正在调试的类即将超出作用域时,你可以调用这段代码来删除它的调试器:
subscription.Dispose();
这对你有用吗?