【问题标题】:Dependency Injection + Ambient Context + Service Locator依赖注入 + 环境上下文 + 服务定位器
【发布时间】:2012-05-21 10:01:44
【问题描述】:

最近我阅读了很多关于应用程序设计模式的内容:关于 D​​I、SL 反模式、AOP 等等。原因——我想在设计上做出妥协:松散耦合、干净且易于使用。 DI 似乎几乎是一种解决方案,除了一个问题:导致构造函数或属性污染的横切和可选依赖项。因此,我为此提出了自己的解决方案,我想知道您对此有何看法

Mark Seemann(DI 书籍的作者和著名的“SL 是反模式”声明)在他的书中提到了一种称为 Ambient Context 的模式。尽管他说他不太喜欢它,但这种模式仍然很有趣:它就像旧的好单例,只是它是作用域的并提供默认值,因此我们不必检查 null。它有一个缺陷——它没有而且它不知道它的范围以及如何处理自己。

那么,为什么不在这里应用服务定位器呢?它可以解决环境上下文对象的作用域和处置问题。在你说它是反模式之前:这是你隐藏合同的时候。但在我们的例子中,我们隐藏了 OPTIONAL 合同,所以 IMO 还不错。

这里有一些代码来说明我的意思:

public interface ILogger
{
    void Log(String text);
}

public interface ISomeRepository
{
    // skipped
}


public class NullLogger : ILogger
{
    #region ILogger Members

    public void Log(string text)
    {
        // do nothing
    }

    #endregion
}

public class LoggerContext
{
    public static ILogger Current
    {
        get
        {
            if(ServiceLocator.Current == null)
            {
                return new NullLogger();
            }
            var instance = ServiceLocator.Current.GetInstance<ILogger>();
            if (instance == null)
            {
                instance = new NullLogger();
            }
            return instance;
        }
    }
}

public class SomeService(ISomeRepository repository)
{
    public void DoSomething()
    {
        LoggerContext.Current.Log("Log something");
    }
}

编辑:我意识到提出不具体的问题与堆栈溢出设计相冲突。因此,我会将一篇最好地描述为什么这种设计不好或更好地提供更好的解决方案(或者可能是添加?)的帖子标记为答案。但是不建议使用AOP,它很好,但是当你真的想在你的代码中做一些事情时,它不是一个解决方案。

编辑 2:我添加了对 ServiceLocator.Current 的检查是否为空。这就是我的代码要做的事情:在未配置 SL 时使用默认设置。

【问题讨论】:

  • 我在您的问题中缺少的是一个示例,您清楚地表明您需要使用 LoggerContext.Current 而不是在代码中注入 ILogger。根据我的经验,如果您需要在整个代码中注入许多 ILogger 依赖项,那么您要么记录太多(而不是抛出异常),要么您没有遵守 SRP(实际上您确实需要 AOP)。看看这个答案:stackoverflow.com/questions/9892137/….

标签: dependency-injection service-locator cross-cutting-concerns


【解决方案1】:

您可以使用手工制作的装饰器或某种拦截(例如Castle DynamicProxyUnity's interception extension)添加横切关注点。

因此您根本不必将ILogger 注入您的核心业务类。

【讨论】:

  • 是的,我到处都读到了同样的建议,但我觉得不合适。有时日志应该写在某个“if”分支的方法中。拦截不能解决这个问题。而 ILogger 只是一个例子,但可以有其他接口带有读取某些值的方法。
  • @DmitryGolubets:如果你认为日志记录是你的类的一个重要方面,那就让它变得显而易见,并使用构造函数注入来注入它。如果它用于跟踪输入值和记录异常(我将记录和跟踪视为根本不同的东西!)使用装饰器或拦截器。但永远不要隐藏依赖!那迟早会咬你。总是。
  • 如果您希望记录器成为封装类逻辑的一部分,也许最好将您的类拆分为几个细粒度的类并装饰它们?
  • 相比少数大类,我更喜欢细粒度的类结构。但是,如果能够使用装饰器进行日志记录是将代码分成更小的部分的唯一原因,我会质疑这种方法。
【解决方案2】:

您提出的环境上下文的一个问题是,它使测试变得更加困难。有几个原因:

  1. 在运行单元测试时,必须始终在“ServiceLocator.Current”中注册一个有效的实例。但不仅如此,它必须使用有效的ILogger 注册。
  2. 当您需要在测试中使用假记录器时(除了简单的 NullLogger),你将不得不配置你的容器,因为那里 无法挂钩,但由于容器是单例,所有其他测试都将使用相同的记录器。
  3. 创建一个可在单元测试并行运行时工作的解决方案将是非常重要的(而且是浪费时间)(正如 MSTest 默认情况下所做的那样)。

所有这些问题都可以通过简单地将ILogger 实例注入到需要它的服务中来解决,而不是使用环境上下文。

如果您系统中的许多类都依赖于 ILogger 抽象,那么您应该认真问问自己 whether you're logging too much

还要注意dependencies should hardly ever be optional

【讨论】:

  • 1.史蒂文,我忘了检查 ServiceLocator.Current 是否为空,感谢您指出这一点。使用此检查测试不会强制初始化 SL。 2. 为什么我首先要测试 ILogger 之类的东西?但如果我愿意,它仍然是可行的。 3. 我同意 - 如果你想测试它,这是一个问题。但同样 - 这是可行的。最后,更重要的是:轻松编写真正的代码还是轻松编写测试?
  • 3.有很多研究证明,如果不进行测试,就无法编写高质量的代码。因此,能够轻松编写测试对于编写高质量的应用程序至关重要。
  • @DmitryGolubets:让我扭转局面。为什么不应该在类中测试 ILogger 的使用?既然你写了这个,这一定是有价值的业务逻辑,你应该想知道这段代码是否正确。如果这段代码没有真正的价值,你为什么不一开始就写呢?也许这条线是一个横切关注点(AOP)并且不是重要的业务逻辑。在这种情况下,你不应该使你的业务逻辑复杂化,你应该把这个逻辑提取到一个装饰器或某种东西中。
  • 关于第 3 点。似乎 MSTest 默认以串行方式运行测试 (stackoverflow.com/questions/154180/…)
猜你喜欢
  • 1970-01-01
  • 2013-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-28
  • 2012-08-25
相关资源
最近更新 更多