这是静态属性的坏用法吗?

时间:2013-01-31 12:00:46

标签: c# design-patterns dependency-injection static-members

如果我有一个具有服务的类,我希望所有派生类都可以访问(例如安全对象或存储库),那么我可能会这样做:

public abstract class A
{
    static ISecurity _security;
    public ISecurity Security { get { return _security; } }

    public static void SetSecurity(ISecurity security) { _security = security; }
}

public class Bootstrapper
{
    public Bootstrapper()
    {
        A.SetSecurity(new Security());
    }
}

最近我觉得静态属性在任何地方都被避开,作为绝对避免的东西。对我来说,这似乎比向我生成的每个派生类的构造函数添加一个ISecurity参数更清晰。鉴于我最近读过的所有内容,我不禁要问:

这是依赖注入的可接受应用还是我违反了一些可能在以后困扰我的主要设计原则?我现在不做单元测试,所以如果我那时候我会突然意识到我的问题的答案。说实话,虽然我可能不会改变我的设计,但如果还有其他一些重要的原因我应该改变它,那么我很可能。

编辑:我第一次编写代码时犯了几个愚蠢的错误......现在已修复了。我以为我会指出这一点,万一有人碰巧注意到了:)。

编辑:SWeko对所有必须使用相同实现的派生类提出了一个很好的观点。在我使用这种设计的情况下,该服务始终是单身,因此它有效地强制执行已有的要求。当然,如果情况并非如此,这将是一个糟糕的设计。

2 个答案:

答案 0 :(得分:2)

由于几个原因,这种设计可能会出现问题。

您已经提到过单元测试,这一点非常重要。这种静态依赖性可以使测试更加困难。当假ISecurity必须是Null Object实现以外的任何其他内容时,您将发现自己必须在测试拆除时删除虚假实现。在测试拆卸过程中将其移除可防止在您忘记移除该假物体时影响其他测试。测试拆卸会使您的测试更加复杂。并没有那么复杂,但是当许多测试拆除代码并且当一个测试忘记运行拆除时你很难找到测试套件中的错误时,这会增加。您还必须确保已注册的ISecurity虚假对象是线程安全的,并且不会影响可能并行运行的其他测试(由于显而易见的性能原因,测试框架(如MSTest并行运行测试)。

将依赖项注入静态的另一个可能的问题是,您强制此ISecurity依赖项是单例(并且可能是线程安全的)。例如,这不允许应用任何具有与单身人士不同的生活方式的拦截器和decorators

另一个问题是从构造函数中删除此依赖项会禁用可由DI框架代表您执行的任何分析或诊断。由于您手动设置此依赖项,因此框架不了解此依赖项。从某种意义上说,您将管理依赖关系的责任转移回应用程序逻辑,而不是允许Composition Root控制依赖关系连接在一起的方式。现在,应用程序必须知道ISecurity实际上是线程安全的。这是一项通常属于组合根的责任。

您希望将此依赖关系存储在基类型中这一事实甚至可能表明违反了一般设计原则:Single Responsibility Principle(SRP)。它与我过去制作的设计错误有些相似之处。我有一组业务操作都是从基类继承的。此基类实现了各种行为,例如事务管理,日志记录,审计跟踪,添加容错和....添加安全检查。这个基类变得无法管理God Object。这是无法管理的,仅仅因为它有太多的责任;它违反了SRP。 Here's my story如果你想了解更多相关信息。

因此,不要在基类中实现这种安全性问题(它可能是一个跨领域的问题),而是尝试一起删除基类并使用装饰器为这些类添加安全性。您可以使用一个或多个装饰器包装每个类,每个装饰器可以处理一个特定的问题。这使得每个装饰器类都易于遵循,因为它们将遵循SRP。

答案 1 :(得分:1)

问题是不是真正的依赖注入,即使它被封装在类的定义中。不可否认,

static Security _security;

会比Security更糟糕,但是A的实例仍然没有使用调用者传递给他们的任何安全性,他们需要依赖静态属性的全局设置。

我想说的是你的用法与:

没有什么不同
public static class Globals
{
   public static ISecurity Security {get; set;}
}