我有一些调试功能,我想重构,但看到它们是调试功能,似乎他们不太可能遵循正确的设计。他们几乎可以深入到应用程序的深处来处理事情。
我的应用程序的主要形式有一个包含调试功能的菜单,我在表单代码中捕获事件。目前,这些方法要求应用程序中的特定对象,如果它不为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 /策略模式。但只有一个实现具有调试功能。
旁白:在此上下文中是否还有另一个“调试”术语,指的是仅测试功能? “调试”通常只是指修复事物的过程,因此很难搜索到这些内容。
答案 0 :(得分:1)
我强烈建议您阅读Ninject的walkthrough of dependency injection(请务必仔细阅读整个教程)。我知道,鉴于你的问题,这似乎是一个奇怪的建议;但是,我认为从长远来看,这将为您节省大量时间并保持代码清洁。
您的调试代码似乎取决于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的提供程序进行访问。顺便说一句,这种技术确实具有表单中较少事件处理程序的额外好处。
顺便说一句,我建议不要为了调试而违反设计原则,并让你的调试“muck with stuff”方法与你的内部类交互,就像任何其他代码必须一样 - 通过它的接口“合同”。你会省事头疼=)
答案 1 :(得分:1)
创建单独的接口以保存调试功能,例如:
public interface IDebugState
{
void ToggleDebugMode(bool enabled); // Or whatever your debug can do
}
您有两个选择,您可以像注入IDebugState
一样注入IGenericState
,如:
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原则,因为你实际上并没有在这里创建任何依赖,只是测试你是否已经有了 - 并且你仍然依赖于对结核的抽象。 / p>
当然,在内部,您仍然SpecificState
同时实施IGenericState
和IDebugState
,因此只有一个实例 - 但这取决于您的IoC容器,您的任何依赖类都不需要知道它。
答案 2 :(得分:0)
正如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();
这对你有用吗?