【问题标题】:Refactoring - Breaking Dependencies重构——打破依赖
【发布时间】:2018-10-05 20:25:06
【问题描述】:

目前我正在处理Racing-Car-Katas。目标是重构大量代码,使其遵循可靠的原则。

我尝试添加Dependency Inversion Principle。我可以通过构造函数传递依赖项。

初始情况

在类Alarm 中是依赖Sensor,它生成一个psiPressureValue

public class Alarm {
    Sensor sensor = new Sensor();

    public void check()
    {
        double psiPressureValue = sensor.popNextPressurePsiValue();
        /* ... */
    }
}

想法

public class Alarm {
    Sensor sensor;

    public Alarm() {
        this.sensor = new Sensor();
    }

    public Alarm(ISensor sensor) {
        this.sensor = sensor;
    }

    public void check()
    {
        double psiPressureValue = sensor.popNextPressurePsiValue();
        /* ... */
    }
}

如果这是一个真正的应用程序,我不想破坏AlarmSensor 之间的任何依赖关系。在那里我会创建以下构造函数

public Alarm() {
    this.sensor = new Sensor();
}

但我的直觉告诉我这是代码异味..

如何在现实世界的应用程序中处理这种依赖关系?

【问题讨论】:

  • "I don't want to break any dependency between Alarm and Sensor"。你的意思是?在现实世界的应用程序中,如果Sensor 是一个具体的类,您将不希望这两者之间存在强耦合。接口和构造注入是要走的路。我不明白你的担心?
  • @plalx 我从链接中读到 OP 正在进行学习练习。

标签: oop design-patterns dependency-injection dependencies refactoring


【解决方案1】:

依赖倒置原则 (DIP) 指出:

高级模块不应依赖于低级模块。

但是,通过定义创建 Sensor 实现的默认构造函数,您违反了 DIP,因为 Sensor 是一个低级模块,而 Alarm(您的高级模块)依赖于它:

public Alarm() {
    this.sensor = new Sensor();
}

如果需要让高级模块依赖于抽象(如您的附加构造函数所示),则无需添加默认构造函数。这样做只会将依赖关系拖到低级模块。由于您的最终应用程序和测试都应该使用重载的构造函数,因此默认构造函数没有任何意义,只会导致紧密耦合,因此违反了 DIP。

这不是理论练习。在实际应用中应遵循 DIP。一个设计良好的实际应用程序应用 SOLID 原则并使用依赖注入作为实现松散耦合和 DIP 的方式。这将模块解耦并允许在Composition Root 中组合完整的对象图。

【讨论】:

    【解决方案2】:

    为了不破坏AlarmSensor 之间的任何依赖关系,您需要:

    1. Sensor所有被Alarm使用的方法;
    2. 将它们放在一个(或多个)接口中;然后,
    3. Sensor 实现这些接口。

    第二种构造方法不是保护依赖关系的正确方法。它通过创建Sensor 类的对象违反了 DIP。

    取而代之的是,将验证代码添加到另一个构造函数,并确保 Alarm 始终通过 ISensor 使用有效的 Sensor(而不是直接创建 Sensor 的实例)。

    【讨论】:

    • “采用警报使用的所有传感器方法”部分是一个非常好的观点(很容易被忽视)。这暗示了接口隔离原则。
    • @Steven: No... :) “Take all...”仅暗示依赖倒置。我正在收集所有方法,以便找出 Sensor 提供的哪些功能是我真正的依赖项,然后使用它来反转对 Sensor 的具体实现的依赖。第二点——“把它们放进……”——暗示了接口隔离。
    • 是的,这两个部分结合在一起,但它实际上是对 ISP 很重要的第一部分,因为正如您正确指出的那样,您不一定希望将所有传感器方法暴露给警报。
    【解决方案3】:

    好的,你想打破警报和传感器之间的直接耦合。

    您提出的解决方案显示了两个构造函数,一个注入 Sensor 对象(在外部创建),一个直接创建 Sensor 对象。你应该放弃:

    public Alarm() {
        this.sensor = new Sensor();
    } 
    

    因为它不需要并且因为它延长了您试图避免与 SOLID 的紧密耦合。这样做不会破坏依赖关系。

    创建依赖项的最常用选项大致如下。 创建依赖对象:

    1) 直接由 Client 对象(紧耦合)

    2) 客户端使用客户端的工厂方法间接

    3) 客户端使用抽象工厂对象间接注入客户端(工厂注入是DI)。你可以注入不同的工厂。

    4) 外部手动注入客户端 (DI)

    5) 通过 DI 容器从外部注入到客户端 (DI)

    DI 的关键点在于客户端无法控制其依赖项的创建方式。这解释了术语“控制反转”。

    但是,客户端通过“Factory Method”、“Abstract Factory”保留控制权。

    DI 容器近年来变得非常流行,尤其是在现代框架中。

    【讨论】:

    • 不确定这是太多还是太少,但是关于这个主题有很多“模式”,如果您想在空闲时间进一步阅读,我已经提到了一些!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-23
    • 2015-08-30
    • 1970-01-01
    • 2020-01-29
    • 2015-10-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多