表单事件进入应用程序的正确方式

本文关键字:方式 应用程序 事件 表单 | 更新日期: 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实现IGenericStateIDebugState,所以只有一个实例 -但这取决于您的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();

这对你有用吗?

相关文章: