依赖注入和 C# 中的保护子句和空对象模式

Guard clause and Null Object Pattern in Dependency Injection and C#

使用构造函数注入,依赖项被注入到消费者中(至少我希望我理解正确):

public class SomeConsumer
{
    private IDependency someDependency;
    public SomeConsumer(IDependency someDependency)
    {
        if (someDependency != null)
        {
            this.someDependency = someDependency;
        }
        else
        {
            throw new ArgumentNullException("someDependency");
        }
    }

    public void Baz()
    {
        someDependency.DoSomething();
    }
    (...)
}

如果我要为 IDependency 使用空对象模式,我需要保护子句吗?还是注入Null Object不对?

更新: 为了澄清,让我们假设我有这样的类和接口:

public interface IDependency
{
    void DoSomething();
}

public class NullDependency : IDependency
{
    public void DoSomething()
    {
        //Do nothing...
    }
}

public class RealDependency : IDependency
{
    public void DoSomething()
    {
        Console.WriteLine("Did something");
    }
}

public class Foo
{
    public void Bar()
    {
        IDependency dependency = new NullDependency();
        SomeConsumer sc = new SomeConsumer(dependency);
        sc.Baz();
    }
}

然后我可以安全地从 SomeConsumer 中删除保护子句,使其看起来像:

public class SomeConsumer
{
    private IDependency someDependency;
    public SomeConsumer(IDependency someDependency)
    {
        this.someDependency = someDependency;
    }

    public void Baz()
    {
        //if someDependency is a NullDependency, this does nothing
        someDependency.DoSomething(); 
    }
    (...)
}

或者我应该使用保护子句,因为我不能确定 null 永远不会被注入?

恕我直言,注入空对象完全没问题,这在测试中经常使用。当我不关心这种依赖性时,我经常在我的单元测试中注入具有默认行为的模拟。

我可能会将 null 检查替换为抛出异常或将其完全删除。它目前不执行任何操作,因为 someDependency 变量的默认值无论如何都是 null

恕我直言,在以下情况下我会放弃保护条款:

  • SomeConsumer 仅在您的产品中使用
  • 你的团队彻底实践了空对象模式and/or依赖注入容器配置

我可能不会放弃保护条款,如果:

  • 没有为目标受众充分记录对空对象的需求
  • SomeConsumer 是开放 API 的一部分,供不了解空对象模式的开发人员使用
  • 我希望在我的依赖项注入容器实例化时收到反馈 SomeConsumer,我犯了一个错误

我在任何情况下都不喜欢放弃保护条款。谁使用这个 class,所有创建的对象都应该是有效的。如果允许通过构造函数注入 null,则允许构造无效对象。稍后,调用方法时会出现问题。

这个class是不是内部不是问题。问题是您是否会确保在调用构造函数时在所有地方采取所有措施来产生非空值?即使您的答案是 "yes",下一个问题是您为什么要浪费时间和精力检查它?

只要留下保护子句,你就会知道这个class的所有对象都会被正确构造。否则,如果您无意中将 null 传递给构造函数,一些完全不相关的 class 将失败,并且您将很难将错误追溯到该构造函数。

在相关说明中,一些保护条款(那些测试非空条件的条款)通常是重新考虑设计的原因。您可能会发现这篇文章很有趣 - Why do We Need Guard Clauses?