设计原则-依赖倒置原则

如同人体结构一样,项目代码也是需要有结构的,如原子逻辑块(不可再分代码块)、方法、类、模块等。结构要么是由成熟的框架搭建起来,要么自己手动划分,但是都需要保证下层模块的变动时不会影响上层模块。注意:这里所说的模块和项目代码结构中不完全一样,这里可以至结构中各个部分,比如原子逻辑块、方法等。
按照生活的正常逻辑来说,上层模块依赖于下层模块(即,“依赖正置”)是没问题的,下层模块实现的功能就是提供给上层模块使用。但是由于需求的不断变化,下层模块功能的变动可能会影响到上层模块,继而导致整体功能的不可用。为解决这个问题,项目的各模块之间需遵循依赖倒置原则,下面我们就逐步讲述下什么是依赖倒置原则,以及如何解决这个问题的。
设计原则-依赖倒置原则_第1张图片

一、依赖倒置原则概念

依赖倒置原则(Dependence Inversion Priciple, DIP)原始定义是:

High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstraction should not depend upon details. Details should depend upon abstractions.

定义内容具体描述了三个设计方面:

  1. 高层模块不应该直接依赖底层模块,两者都应该依赖其抽象。
  2. 抽象不应该依赖于细节
  3. 细节应该依赖于抽象

设计原则-依赖倒置原则_第2张图片
根据在软件设计中"加一层"的思路办法,这里依赖倒置也是如此,两个模块直接依赖可能会有问题,那就引入抽象层。什么是抽象?什么是细节呢?直观来说,在Java语言中,抽象就是指接口或抽象类,均不能实例化;细节就是具体实现类。
将依赖上升到抽象层。为保证行为(功能)和之前类似,抽象和具体实现细节也有两个约束。第一个是抽象不依赖于细节,这表明了细节的改动不应该影响到抽象含义的变化,上层模块的影响评估基本按照抽象含义的变动范围进行评估。第二个是细节应该依赖于抽象,这表明具体实现细节应该符合抽象含义,抽象含义变化时,细节也需要跟随改动。
回头来看,“依赖倒置”并不是说模块之间的依赖倒置,模块之间依然是上层依赖于下层。这里的倒置指的是抽象和细节之间的依赖关系倒置了。这种倒置关系,在面向对象设计中就是面向接口编程。因此,设计原则中的"依赖倒置原则"基本和"面向接口编程"含义一致。

[TODO]:后续补充面向对象设计、设计原则|设计模式之间的关系&异同点

二、应用实践

前面大概理解了依赖倒置原则的相关概念和含义之后,本节根据具体示例来体会下应用依赖倒置原则的优点。
以当下比较流行的直播带货场景为例,在这个场景中考虑两个模块:

  • 直播管理模块:负责管理直播间的创建、修改直播信息、查询搜索等功能
  • 商品供应模块:负责提供商品的信息和库存。

很明显,直播管理模块需要依赖商品供应模块提供的商品能力,因此这里前者就是上层模块,而后者就是下层模块。

2.1 直接依赖

在没有应用依赖倒置原则时,直播管理模块会直接依赖于具体的商品供应模块的实现。代码如下:
淘宝商品供应模块

public class TaobaoSupplier {
    public ProductInfo getProductInfo(String productId) {
        // 调用淘宝供应商的接口获取商品信息
        // ...
        return null;
    }

    public int getProductStock(String productId) {
        // 调用淘宝供应商的接口获取商品库存
        // ...
        return 0;
    }
}

② 直播管理模块-直接依赖淘宝商品供应模块

public class LiveRoomManager {
    private TaobaoSupplier supplier;

    public LiveRoomManager(TaobaoSupplier supplier) {
        this.supplier = supplier;
    }

    public void createLiveRoom(String roomId) {
        // 创建直播间的逻辑

        // 获取商品信息和库存
        ProductInfo productInfo = supplier.getProductInfo("1001");
        int productStock = supplier.getProductStock("1001");

        // 其他逻辑
    }
}

设计原则-依赖倒置原则_第3张图片
如上所示,直播管理模块直接依赖商品供应模块,这种强耦合关系满足最初业务是没有问题。如果之后业务扩展,底层商品供应渠道扩增,比如增加美团商家供应商、永辉超市供应等。那么这些属于商品供应链的改动也需要直播管理模块跟随改动。之前几篇文章也几乎都提到过,只要是变动,就会出现系统问题出现的风险,即系统稳定性变差。

2.2 应用“依赖倒置”原则

为了降低模块之间的耦合性,根据依赖倒置原则,我们新增抽象层,包括直播管理抽象类以及商品供应抽象类。因此,模块之间的依赖均建立在抽象类之间。对应的实现代码如下:
① 下层模块-抽象类

// 定义抽象的商品供应商接口
public interface Supplier {
    ProductInfo getProductInfo(String productId);
    int getProductStock(String productId);
}

② 下层模块-具体实现细节

// 实现具体的商品供应商类
public class TaobaoSupplier  implements Supplier  {

    public ProductInfo getProductInfo(String productId) {
        // 调用淘宝供应商的接口获取商品信息
        // ...
        return null;
    }

    public int getProductStock(String productId) {
        // 调用淘宝供应商的接口获取商品库存
        // ...
        return 0;
    }
}

③ 上层模块-抽象类

// 定义抽象的直播间管理接口
public abstract class LiveRoomManager {
    protected Supplier supplier;

    abstract void createLiveRoom(String roomId);
}

④ 上层模块-具体实现细节

// 实现具体的直播间管理类
public class DefaultLiveRoomManager extends LiveRoomManager {
    
    public DefaultLiveRoomManager (Supplier supplier) {
        this.supplier = supplier;
    }

    public void createLiveRoom(String roomId) {
        // 创建直播间的逻辑

        // 获取商品信息和库存
        ProductInfo productInfo = supplier.getProductInfo("1001");
        int productStock = supplier.getProductStock("1001");

        // 其他逻辑
    }
}

设计原则-依赖倒置原则_第4张图片
如上,当下层模块增加供应商时,上游无需改动,原因是Supplier抽象含义未发生变化。满足依赖倒置原则的好处就是降低了不同模块之间的耦合紧密度。上层模块仅需依赖下层抽象接口,不需要关心具体实现细节,自然也就不需要关注其中发生的变化,提高了系统的灵活性和可扩展性。
有个疑问,从类图中看上层具体实现类DefaultLiveRoomManager确实"直接"依赖了下层模块Supplier,那这个是否违背了依赖倒置的第一条直接依赖的规则?我认为这个不算直接依赖,从含义上讲,上层具体实现类(细节)是通过依赖其抽象,才依赖了下层模块,因此这不算直接依赖。

三、依赖

依赖倒置原则基本已经讲解清楚了,这个小节想写下大家可能会轻易忽略的模糊概念-依赖。依赖语言层面上是指依靠别人或事物而不能自立或自给,和代码世界中一样,生活着我们也是互相依赖、共同协作完成一系列重要任务。因此,依赖他人或他人的能力能够帮助我们解决很多事情,方便我们更加关注自身负责的事情。然而,在代码世界中,依赖似乎也是一把双刃剑,虽然能够通过封装、依赖使得系统结构清晰,但是依赖的不正当应用在面对复杂的业务变动中,会对系统的稳定性、可扩展性产生不好的影响。
不同代码模块之间或服务之间,我们常常会说强依赖、弱依赖,对于前者我们会谨慎对待,甚至对于返回的结果常常会添加多层数据校验逻辑以保证自身系统的稳定性。在面向对象中,类间的依赖关系可有以下几种:

  • 泛化:泛化描述的是类之间的继承关系。
  • 实现:实现指的是类实现接口之间所有功能。
  • 聚合:聚合表示整体与部分之间的关系,但是整体与部分不是强依赖的。即部分不存在时,整体依然可以是存在的。
  • 组合:组合也表示整体与部分之间的关系,但是整体与部分是强依赖的。即整体存在的前提是部分也必须存在。
  • 依赖:依赖表示实体之间在运行时会被依赖者会影响依赖者的行为。
  • 关联:关联表示不同类对象之间的关联性,这是一种静态

这几个关系中,重点关注下聚合、组合、依赖的区别。参考博客链接:https://www.cnblogs.com/jiqing9006/p/5915023.html

你可能感兴趣的:(设计模式(JAVA),依赖倒置原则,设计模式,java)