inversion-of-control - 惰性初始化 - 如何使其成为干净的代码并消除硬依赖?

标签 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,还有什么方法可以使代码干净并与依赖项分离?

最佳答案

这个例子发生的事情是它违反了 Single Responsibility PrincipleDependency Inversion Principle . Robert Martin 已经在示例之后声明:

Having both of these responsibilities means that the method is doing more than one thing, so we are breaking the Single Responsibility Principle.

他还谈到了依赖倒置原则:

we now have a hard-coded dependency on MyServiceImpl and everything its constructor requires.

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

这个问题的解决方案不是使用 IoC 容器或工厂。这里的解决方案是应用依赖注入(inject)模式并:

have a global, consistent strategy for resolving our major dependencies.

如果我们应用 Dependency Injection pattern ,我们的类将变得更简单,像这样:

public class Consumer
{
    private Service service;

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

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

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

上面的例子似乎暗示我们在这里丢失了惰性初始化,但事实并非如此。我们只是将惰性初始化的责任转移到了“全局一致策略”,也就是 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(以及可能的许多其他组件)对一无所知服务 实现是一个重量级对象。这甚至会阻止我们让我们的 Consumer 依赖于第二个 ServiceFactory 抽象。

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

Abstractions should not depend on details.

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

关于inversion-of-control - 惰性初始化 - 如何使其成为干净的代码并消除硬依赖?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28780224/

相关文章:

spring - 导入就地插件的Grails应用程序上下文

c#-3.0 - 扩展方法会隐藏依赖关系吗?

javascript - 为什么 jQuery 没有加载?

c# - 如何将已解析的实例从控制反转传递给应用程序中的类?

Jquery 延迟加载与 ajax

javascript - 结合 lazySizes 和 smoothState 插件时的大量 http 请求

visual-studio - 企业应用程序中的 Ioc 容器放置

c# - 如何正确地将拦截器添加到没有 ILogger 类型的字段或属性的组件

c# - AutoFac 生命周期 : Force new instance in specific situation

.net - 如何使用统一 IoC 容器