2

最近我读了很多关于应用程序设计模式的东西:关于DI,SL反模式,AOP等等。其原因 - 我想达成设计的妥协:松散耦合,干净,易于使用。除了一个问题之外,DI似乎几乎是一种解决方案:跨领域和可选的依赖导致构造或财产污染。所以我来为我自己的解决方案和我想知道你怎么看它依赖注入+环境上下文+服务定位器

Mark Seemann(DI书的作者,着名的“SL is anti-patter”声明)在他的书中提到了一种叫做Ambient Context的模式。虽然他说他不太喜欢它,但这种模式仍然很有趣:它就像旧的单身人士,除了它是作用域并提供默认值的,所以我们不必检查null。它有一个缺陷 - 它没有,它不知道它的范围和如何处理它自己。

那么,为什么不在这里应用服务定位器?它可以解决范围和处理环境上下文对象的问题。在你说它是反模式之前:这是你隐藏合约的时候。但在我们的情况下,我们隐藏可选合约,所以它不是那么糟糕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时使用默认设置。

+0

我从你的问题中遗漏了一个例子,你清楚地表明你需要使用'LoggerContext.Current'而不是在代码中注入一个'ILogger'。根据我的经验,如果您需要在代码中注入许多'ILogger'依赖项,您要么记录太多(而不是抛出异常),或者您没有遵守SRP(实际上您需要AOP)。看看这个答案:http://stackoverflow.com/questions/9892137/windsor-pulling-transient-objects-from-the-container。 – Steven

回答

3

作为您所提议的环境环境的问题是,它使测试变得更加困难。出于以下原因:

  1. 运行单元测试时,必须始终在“ServiceLocator.Current”中注册一个有效的实例。但不仅如此,它必须注册有效的​​。
  2. 当你需要在一个测试中使用假记录器(除了简单的 NullLogger),你将不得不配置你的容器,因为 没有办法挂钩到这个,但由于容器是一个单身,所有其他测试将使用相同的记录器。
  3. 这将是非平凡的(并浪费时间)来创建一个解决方案,当您的单元测试并行运行时(如MSTest默认)。

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

并且如果系统中的许多类取决于那个抽象的​​,那么您应该认真询问您的自我whether you're logging too much

另请注意,dependencies should hardly ever be optional

+0

1. Steven,我忘了检查ServiceLocator.Current为null,谢谢指出。通过这个检查测试不会强制初始化SL。 2.为什么我应该首先测试像ILogger这样的东西?但如果我想它仍然可行。 3.我同意 - 如果你想测试它,这是一个问题。但是,再次 - 这是可行的。最后,更重要的是:轻松写出真实的代码还是简单地编写测试? –

+1

3.有很多研究证明,如果不能测试它,就不能编写高质量的代码。所以能够轻松编写测试对于编写高质量的应用程序至关重要。 –

+1

@DmitryGolubets:让我转过来。你为什么不想在一个类中测试一个ILogger的用法?既然你写了这个,这必须是有价值的业务逻辑,你应该想知道这段代码是否正确。如果这个代码不是很有价值,为什么不把它写在第一位呢?也许这条线是一个横切关注点(AOP),并不是重要的业务逻辑。在这种情况下,你不应该使业务逻辑复杂化,你应该将这个逻辑提取到装饰器或某种类型。 – Steven

3

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

因此,您不必将​​注入核心业务课程。

+0

是的,我到处都读了同样的建议,但对我来说并不合适。有时日志应该写在某个“if”分支中的方法内部。拦截并不能解决这个问题。 ILogger只是一个例子,但是可以有其他接口来读取一些值。 –

+2

@DmitryGolubets:如果您认为日志记录是您班级的一个重要方面,那就明确一点,并使用构造函数注入来注入它。如果它用于跟踪输入值和日志记录异常(我将日志和跟踪视为根本不同的事情!)使用装饰器或拦截器。但永远不要隐藏依赖!那会比以后更快地咬你。总是。 –

+0

如果你想让记录器成为封装类逻辑的一部分,也许最好是把你的类拆分成几个细粒度的类,然后分别装饰它们? –