延迟初始化 - 如何使其成为一个干净的代码并删除硬依赖?

时间:2015-02-28 09:32:24

标签: inversion-of-control lazy-loading ioc-container factory-pattern code-cleanup

在书的第11章"清洁代码:敏捷软件工艺手册"中,Bob叔叔说下面的懒惰初始化并不是一个干净的代码。它需要承担两个责任,并且具有很强的依赖性。

public Service getService() {
    if (service == null)
       service = new MyServiceImpl(...); // Good enough default for most cases?
    return service;
}

除了IoC Container和Factory之外,有没有办法让代码干净并用依赖关系分开?

1 个答案:

答案 0 :(得分:6)

此示例的用途是违反了Single Responsibility PrincipleDependency Inversion Principle。罗伯特马丁已经在例子之后说明了:

  

具有这两个职责意味着该方法正在进行   不止一件事,所以我们打破了单一责任   原理

他还通过陈述:

来触及依赖性倒置原则
  

我们现在对MyServiceImpl以及所有内容都有一个硬编码依赖项   它的构造函数需要。

拥有这种硬编码的依赖意味着打破 依赖倒置原则。

此问题的解决方案与使用IoC容器或工厂无关。这里的解决方案是将依赖注入模式应用于:

  

为解决我们的专业问题制定全球一致的战略   的依赖关系。

如果我们应用Dependency Injection pattern,我们的课程会变得更简单,就像这样:

public class Consumer
{
    private Service service;

    public Consumer(Service service) {
        this.service = service;
    }

    public void SomeMethod() {
        // use service
    }
}

请注意,Consumer现在不再通过其公共方法公开Service。这不应该是必需的,因为模块不应该共享其内部状态,如果某个其他组件需要使用我们的Service,我们可以直接将它注入到其他组件中。

上面的示例似乎暗示我们在这里丢失了延迟初始化,但事实并非如此。我们只是将延迟初始化的责任转移到了“全局,一致的策略”,a.k.a。Composition Root

由于Service是一个抽象,我们可以创建一个只为我们的MyServiceImpl实现延迟初始化的代理(延迟初始化将是它的唯一责任)。这样的代理可以看起来如下:

internal class LazyServiceProxy : Service
{
    // Here we make use of .NET's Lazy<T>. If your platform doesn't
    // have this, such type is easily created.
    private Lazy<Service> lazyService;

    public LazyServiceProxy(Lazy<Service> lazyService) {
        this.lazyService = lazyService;
    }

    public void ServiceMethod() {
        // Lazy initialization happens here.
        Service service = this.lazyService.Value;
        service.ServiceMethod();
    }
}

这里我们创建了一个LazyServiceProxy,其唯一目的是推迟创建真实服务。它甚至不需要“MyServiceImpl的硬编码依赖及其构造函数所需的一切”。

在我们的合成根中,我们可以轻松地将所有内容连接在一起,如下所示:

Service service = new LazyServiceProxy(
    new Lazy<Service>(() => new MyServiceImpl(...)));

Consumer consumer = new Consumer(service);

在这里,我们将应用任何延迟初始化的责任移到了应用程序的启动路径上,我们保持或Consumer(可能还有许多其他组件)清除了对Service实现的了解成为重量级人物。这甚至阻止我们让Consumer取决于第二个ServiceFactory抽象。

不仅使Consumer这个额外的工厂抽象更复杂,它在这种特定情况下打破了依赖性倒置原则,因为MyServiceImpl是一个重量级对象的事实,是一个实现详细信息因此我们通过工厂抽象泄漏实现细节。这违背了依赖性倒置原则:

  

抽象不应该依赖于细节。

如您所见,此解决方案不需要IoC容器(如果您愿意,仍然可以使用IoC容器)并且不需要工厂。虽然工厂设计模式在应用依赖注入时仍然有效,但您会看到正确应用SOLID和依赖注入将大大减少使用工厂的需要。