C# 返回一个";“手动超控”;从工厂获取的值,检查null或布尔开关是否更好?

C# 返回一个";“手动超控”;从工厂获取的值,检查null或布尔开关是否更好?,c#,C#,假设我们有一个类似的方法 public static IThing getTheThing() { return internalThingGetter(); } 为了调试或单元测试的目的,我们想引入一种轻量级策略来手动覆盖它 private static IThing _thingManualOverride; public static IThing getTheThing() { if (/*some condition*/) return _thing

假设我们有一个类似的方法

public static IThing getTheThing() {
    return internalThingGetter();
}
为了调试或单元测试的目的,我们想引入一种轻量级策略来手动覆盖它

private static IThing _thingManualOverride;

public static IThing getTheThing() {
    if (/*some condition*/)
        return _thingManualOverride;
    else
        return internalThingGetter();
}
检查
\u thingManualOverride!=null
或引入新的布尔值并检查,例如,
\u应覆盖

或者,这里有更坚实的图案吗

编辑:一些需要满足的目标:

  • 假设保持接口是重要的。很多代码都使用这种静态方法,修改它是一个不错的目标,但代价高昂
  • 这两个检查都是“正确的”,因为如果覆盖为
    null
    ,我们可以安全地假设默认路线是正确的

为了单元测试的目的,最好在工厂里摆脱静态方法,而采用虚拟方法。这样,您就可以使用mock替换其返回值

class Factory{    
   public virtual IThing getTheThing() {
      return internalThingGetter();
   }
}
在测试中,使用Moq框架:

var m = new Mock<Factory>();
m.Setup(f=>f.getTheThing())
 .Returns(thingManualOverride);
var m=new Mock();
m、 设置(f=>f.getTheThing())
.返回(thingManualOverride);

如果您选择
\u thingManualOverride!=空
。您可以检查另一个字段,比如一个名为“\u IsthingManualLoverrideset”的布尔值,但由于程序员的错误,它们可能会给出相互冲突的值:

_thingManualOverride = null;
_IsThingManualOverrideSet = true; // <-- oops!
\u thingManualOverride=null;

_IsThingManualOverrideSet=true;//这不是一个答案,只是一个建议。阅读Roy Osherove写的第三章对你有好处(更好的是,出去买一本,读一读整本书——太棒了!)。特别是第3.4.5节(以下是包含该节的示例第3章的链接):

更新:根据您对使用静态factory类示例的请求:

static class ThingFactory
{
  private static IThing _thingManualOverride = null;

  public static IThing getTheThing() 
  {
    if (_thingManualOverride != null)
      return _thingManualOverride;

    return new internalThingGetter();
  }

  public static void SetThing(IThing thing)
  {
    _thingManualOverride = thing;
  }
}

<>我会补充说,我不喜欢使用这个模式。如果你有机会重构,考虑它可能是明智的。我不喜欢使用这个的主要原因是因为你必须记住<强>重置状态< /强>(调用带有空参数的SETTASE方法)在每次测试之后,这样您就不会用手动覆盖影响其他测试。

您可以创建一个帮助器类来为您执行此操作,可能类似于:

public class OverridableValue<T>
{
    private readonly Func<T> OriginalValueGetter;
    private Func<T> ValueGetter;

    public OverridableValue(Func<T> valueGetter)
    {
        this.OriginalValueGetter = valueGetter;
        this.ValueGetter = OriginalValueGetter;
    }

    public T Value
    {
        get 
        {
            return ValueGetter();
        }
    }

    public void SetValueOverride(T value)
    {
        if (value == null)
            ValueGetter = OriginalValueGetter;
        else
            ValueGetter = () => value;
    }
}
您可以轻松地将
null
签入
SetValueOverride
替换为其他内容,或者简单地定义一个新方法来
RemoveOverride()
,当
null
值被视为有效覆盖时,该方法会有所帮助:

public void SetValueOverride(T value)
{
    ValueGetter = () => value;
}

public void RemoveOverride()
{
    ValueGetter = OriginalValueGetter;
}
另一个好处是(我认为)这种覆盖通常应该是线程安全的(您不会遇到这样的情况,即一些
IsOverriddenFlag
true
但没有覆盖值)

编辑:您可以在
value
setter中进行值重写,但我认为这在实践中看起来很奇怪:

public T Value
{
    get 
    {
        return ValueGetter();
    }
    set
    {
        if (value == null)
            ValueGetter = OriginalValueGetter;
        else
            ValueGetter = () => value;
    }
}

myThing = _thingManualOverride.Value; //Thing1
_thingManualOverride.Value = new Thing2();
myThing = _thingManualOverride.Value; //Thing2
_thingManualOverride.Value = null;
myThing = _thingManualOverride.Value; //Thing1 ... not null!?

我不认为这是对他的问题的回答。我确实同意你的方法要好得多,但他并没有要求提供改进单元测试的一般提示。@MartinMulder,我认为这正是他所要求的——“或者,这里是否有更可靠的模式可供使用?”?“@alex,谢谢你,虽然我同意这对一般的单元测试来说更好,但我正试图弄清楚如果我真的需要保持当前的接口,该怎么办。因此,是的,这本身就是一个很好的答案,可能不应该得到-1,但我可能不会接受,因为它没有回答问题。对于过路人来说,这是一个有效的出发点。谢谢!我现在手头有一本。我现在会翻到3.4.5,我想这正是我想要的。如果您可以提供一些代码示例,在工厂中封装这个,并保留上面的静态接口,我将接受这个答案。我现在就开始编码!谢谢,我甚至可以提取第二个工厂,用静态方法包装它。谢谢你的警告。在我的例子中,有测试和重构的正交目标。但是其他阅读此答案的人应该知道在新代码中使用此选项的陷阱。我假设
null
对于
IThing\u thingManualOverride
?@ChrisSinclair极好的观点是无效的。我做了一些编辑,希望能澄清这一点。另外,当你说“轻量级策略”时,你是说它运行时高效/快速,还是说它易于编写/维护/复制?这似乎可以归结为创建一个新层来做同样的事情。这似乎把同一个问题放在了另一个地方。投票赞成强调清晰性和意图。然而,考虑到这一点,布尔值并不是那么糟糕,因为它强制了意图,但它确实复制了意图。它变成了一个双钥匙点火开关,有点干燥。
public void SetValueOverride(T value)
{
    ValueGetter = () => value;
}

public void RemoveOverride()
{
    ValueGetter = OriginalValueGetter;
}
public T Value
{
    get 
    {
        return ValueGetter();
    }
    set
    {
        if (value == null)
            ValueGetter = OriginalValueGetter;
        else
            ValueGetter = () => value;
    }
}

myThing = _thingManualOverride.Value; //Thing1
_thingManualOverride.Value = new Thing2();
myThing = _thingManualOverride.Value; //Thing2
_thingManualOverride.Value = null;
myThing = _thingManualOverride.Value; //Thing1 ... not null!?