DDD架构中的依赖倒置的理解

1. 首先,我们看一个“反面教材”(违反DIP的设计)

假设我们要设计一个简单的系统,让用户可以通过不同的开关(比如一个按钮、一个遥控器)来控制不同的灯。

一个很直觉但糟糕的设计如下:


List item

// 高层模块:开关
class Button {
    private Lamp lamp; // Button类直接依赖了一个具体的Lamp类

    public Button(Lamp lamp) {
        this.lamp = lamp;
    }

    public void press() {
        // ... 一些其他逻辑 ...
        if (/* 某种条件 */) {
            lamp.turnOn(); // 直接调用Lamp的方法
        } else {
            lamp.turnOff();
        }
    }
}

// 低层模块:灯
class Lamp {
    public void turnOn() {
        System.out.println("灯亮了");
    }
    public void turnOff() {
        System.out.println("灯灭了");
    }
}

这个设计有什么问题?

  1. 紧耦合(Tight Coupling)Button 类直接依赖于具体的 Lamp 类。Button 的代码里写死了“我只能控制灯”。
  2. 难以扩展:如果有一天,我们想让这个按钮去控制电风扇或者空调,怎么办?我们只能去修改 Button 类的源代码,把 Lamp lamp 改成 Fan fanAirConditioner ac。这违反了“对修改关闭,对扩展开放”的开闭原则。
  3. 高层依赖低层:在这个设计中,高层策略模块(Button,它定义了“按下开关”这个抽象策略)反而依赖于低层实现细节模块(Lamp)。这就像是将军(高层)的作战计划要根据一个士兵(低层)的装备来制定,非常不合理。

2. 现在,我们用“依赖倒置”原则来重构

依赖倒置原则的两条核心定义:

  1. 高层模块不应该依赖低层模块,两者都应该依赖于抽象
  2. 抽象不应该依赖细节,细节应该依赖抽象

如何应用?

第1步:创建一个抽象接口(两者都依赖它)

这个接口应该抽象出高层模块想要控制低层模块的方式。对于 Button 来说,它不关心你到底是灯还是风扇,它只关心“你能不能被我打开和关闭”。

所以我们创建一个 SwitchableDevice 接口:

// 抽象接口
public interface SwitchableDevice {
    void turnOn();
    void turnOff();
}

第2步:让低层模块实现这个抽象接口

现在,Lamp 不再是一个简单的类,它成为了抽象接口的一个“实现细节”。

// 低层模块:实现抽象接口
class Lamp implements SwitchableDevice {
    @Override
    public void turnOn() {
        System.out.println("灯亮了");
    }
    @Override
    public void turnOff() {
        System.out.println("灯灭了");
    }
}

// 我们可以轻松地添加新的低层模块,而无需修改任何现有高层代码
class Fan implements SwitchableDevice {
    @Override
    public void turnOn() {
        System.out.println("风扇开始转动");
    }
    @Override
    public void turnOff() {
        System.out.println("风扇停止了");
    }
}

第3步:让高层模块依赖抽象接口,而不是具体实现

修改 Button 类,让它不再依赖 Lamp,而是依赖 SwitchableDevice 这个接口。

// 高层模块:依赖抽象接口
class Button {
    // 现在它依赖的是一个抽象,而不是一个具体的实现
    private SwitchableDevice device;

    // 通过构造器注入依赖(这就是依赖注入,是实现依赖倒置的常用方法)
    public Button(SwitchableDevice device) {
        this.device = device;
    }

    public void press() {
        // ... 一些其他逻辑 ...
        if (/* 某种条件 */) {
            device.turnOn(); // 它调用的是接口的方法,根本不知道后面是灯还是风扇
        } else {
            device.turnOff();
        }
    }
}

3. 看看现在的效果(“倒置”在哪里?)

现在我们来组装和使用这个系统:

public class Main {
    public static void main(String[] args) {
        // 控制灯
        SwitchableDevice myLamp = new Lamp();
        Button button1 = new Button(myLamp);
        button1.press(); // 输出“灯亮了”

        // 控制风扇:我们不需要修改Button的任何代码!
        SwitchableDevice myFan = new Fan();
        Button button2 = new Button(myFan);
        button2.press(); // 输出“风扇开始转动”

        // 未来如果想控制空调,只需要创建一个AirConditioner类实现SwitchableDevice接口即可。
        // Button类永远不需要再被修改。
    }
}

“倒置”体现在哪里?

  • 传统依赖关系:高层 Button -> 低层 Lamp(从上到下)
  • 倒置后的依赖关系
    • 高层 Button -> 抽象 SwitchableDevice <- 低层 Lamp
    • 依赖关系的箭头不再是从上到下,而是同时指向了中间的抽象层。高層和低層的依赖方向被“倒置”了,它们现在处于同一水平位置,都依赖于一个稳定的抽象。

总结

你可以把依赖倒置理解为一种“中介”或“合同”模式。

  • 抽象接口(SwitchableDevice)就是一份合同。它规定了你必须提供“打开”和“关闭”的服务。
  • 高层模块(Button)是合同的甲方。它说:“我不管你是谁,只要你签了这份合同(实现了接口),我就可以按合同要求你做事。”
  • 低层模块(Lamp, Fan)是合同的乙方。它们签署合同(实现接口),并具体完成合同规定的任务。

这样做的好处巨大:

  1. 解耦ButtonLamp 完全解耦,它们之间唯一的联系就是合同。
  2. 可扩展性:可以无限添加新的“乙方”(新的设备),而完全不用修改“甲方”(Button)的代码。
  3. 可测试性:在测试 Button 时,你可以很容易地创建一个“ Mock Device”(模拟设备)来实现接口,从而孤立地测试 Button 的逻辑。

DDD架构中的依赖倒置

在DDD架构中要实现业务逻辑,我们没有专门的业务层去调用各种接口(外部接口,我把他当成勒我们所需要的依赖),当功能越来越完善的时候我们就会发现业务层写得很乱,所以我们就根据依赖倒置的原则,外部接口的实现我们下掉到infrastructurec层,就是把依赖提取出来噻。然后再业务中调用接口,关注业务即可

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值