依赖倒置原则就看这篇,7张图解彻底吃透,架构设计筑基必知必会

来源:mikechen的互联网架构

依赖倒置是确保架构设计灵活性、扩展性的重要原则。

把依赖方向搞反,是经常出现的典型错误。依赖关系没处理好,就会导致一个小改动,影响一大片。

结构化的编程思路,是自上而下进行功能分解,再按照分解结果进行组合的。这个思路很自然就延续到了很多人的编程习惯中。

本文,我们将通过手工图解、源码示例,来全面解析依赖倒置原则。

大家好,我是 mikechen。

依赖倒置是良好架构设计的基石,社/校招面试常问,必知必会非常重要。为方便大家系统学习,我已将本文归纳到《阿里架构师进阶专题合集》。需要的同学,拉到文末自取。


01
   什么是依赖倒置原则

依赖倒置原则(DIP),英文全称 Dependency Inversion Principle。

顾名思义,就是将传统的依赖关系颠倒过来,让高层模块和底层模块都依赖于抽象接口。

依赖倒置之所以被提出,是源自它所提倡的软件设计原则。


02
  依赖倒置的 UML 类图


03
  依赖倒置的核心思想

依赖倒置原则的两个核心思想:

1) 高层模块不应依赖于底层模块,高层模块、底层模块都应依赖于抽象

2) 抽象不应该依赖于细节,细节应该依赖于抽象

这意味着,

在软件设计中,应该使用抽象类、接口或抽象方法等抽象层,来定义模块之间的通信接口。而具体实现,应该依赖于这些抽象。

一图释义:

 抽象的关键在于:

  • 它提供了一种通用的、高层次的方式,来定义模块之间的接口和交互,而不关心具体的实现。


  • 高层模块将依赖于这个抽象,而不是直接依赖于底层模块的具体细节。


在依赖倒置原则中,抽象可以通过抽象类、接口或抽象方法来实现。

 

04
  依赖倒置原则的作用

依赖倒置改变了传统的依赖关系

依赖倒置从具体细节转移到了抽象上,极大提高了系统的灵活性、可维护性和可扩展性。

在传统的依赖关系中,高层模块直接依赖于底层模式的实现细节。

高层模块需要了解并依赖于底层模块的内部细节,包括其数据结构、算法和逻辑。

由于高层模块与底层模式是一种紧耦合的关系,当底层模块结构发生变化时,高层就需要随之改变。

一个小的变更,可能导致大范围的代码修改和测试。

传统依赖的架构设计很不合理,系统结构太刚性,难以维护及扩展。

传统依赖 VS 依赖倒置:


05
  依赖倒置的实例

再来对比下传统依赖、依赖倒置的实现。

假设:

现在你需要实现一个面包店,你第一件想到的事情是什么?

我想到的是一个面包店,里面有很多具体的面包。

例如:法棍面包、全麦面包、白面包、牛角面包、荞麦面包、法式面包、甜甜圈面包、裸麦面包。

传统依赖关系:

面包店就是上层模块,面包是下层模块。如图:

面包店(Bakery)直接依赖于具体的面包类型(FrenchBaguette 和 WholeWheatBread),这是传统的依赖关系。

当增加底层模块(面包类型)时,对高层模块(面包店)有何影响呢。

















































class Bakery {    private FrenchBaguette frenchBaguette;    private WholeWheatBread wholeWheatBread;
   public Bakery() {        frenchBaguette = new FrenchBaguette();        wholeWheatBread = new WholeWheatBread();        // 初始化具体的面包类型    }
   public void serveBread() {        frenchBaguette.serve();        wholeWheatBread.serve();        // 提供其他具体面包类型    }}
class FrenchBaguette {    public void serve() {        System.out.println("法棍面包供应中");    }}
class WholeWheatBread {    public void serve() {        System.out.println("全麦面包供应中");    }}
// 假设现在需要添加一种新的面包类型,例如牛角面包class Croissant {    public void serve() {        System.out.println("牛角面包供应中");    }}
public class Main {    public static void main(String[] args) {        Bakery bakery = new Bakery();        bakery.serveBread();
       // 现在需要添加新的面包类型(牛角面包)        Croissant croissant = new Croissant();        croissant.serve();
       // 这里需要手动修改Bakery类,将新的面包类型整合进去    }}

如果要添加新的面包类型,例如 Croissant,就需要修改 Bakery 类的代码,将新的面包类型整合进去。

显然,这种方式不够灵活,增加了维护成本和代码的脆弱性。

依赖倒置:

我们不希望让面包店理会这些具体类,于是,重新设计面包店:

  • 创建一个抽象的面包接口,让面包店依赖于这个抽象接口,不再依赖于具体的面包种类。


  • 既然法棍面包、牛角面包、白面包等都是面包,就让它们共享一个面包接口,抽象化一个面包类。


如图所示:

面包店是高层模块,面包类是抽象接口,而各种具体的面包是底层模块。

现在,我们需要增加新的面包类型(底层模块)。

























































// 面包接口,作为高层模块和底层模块之间的抽象interface Bread {    void serve();}
// 具体面包类实现面包接口,作为底层模块class FrenchBaguette implements Bread {    public void serve() {        System.out.println("法棍面包供应中");    }}
class WholeWheatBread implements Bread {    public void serve() {        System.out.println("全麦面包供应中");    }}
// 假设需要添加一种新的面包类型,例如牛角面包class Croissant implements Bread {    public void serve() {        System.out.println("牛角面包供应中");    }}
// 高层模块,面包店class Bakery {    private Bread bread;
   public Bakery(Bread bread) {        this.bread = bread;    }
   public void serveBread() {        bread.serve();    }}
public class Main {    public static void main(String[] args) {        // 创建面包店并提供不同类型的面包        Bread frenchBaguette = new FrenchBaguette();        Bread wholeWheatBread = new WholeWheatBread();
       Bakery bakery1 = new Bakery(frenchBaguette);        bakery1.serveBread();
       Bakery bakery2 = new Bakery(wholeWheatBread);        bakery2.serveBread();
       // 新增的面包类型(牛角面包)不会影响面包店        Bread croissant = new Croissant();        Bakery bakery3 = new Bakery(croissant);        bakery3.serveBread();    }}

面包店(Bakery)依赖于抽象的 Bread 接口,而不依赖于具体的面包类型。

当需要新增一种面包类型(例如Croissant)时,不会对高层模块(Bakery)产生影响,因为高层模块依赖于抽象接口。

对于具体的实现类我们不管,只要接口的行为不发生变化,增加新的面包类后,上层服务不用做任何的修改。

这样的设计降低了层与层之间的耦合,能很好地适应需求的变化,从而提高了系统的可扩展性和可维护性。


总结
  

依赖倒置原则通过抽象(接口或抽象类),让各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合。

依赖倒置原则的核心思想是:高层模块不应依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

遵循依赖倒置原则,通过降低层与层之间的耦合,可以很好地适应需求的变化,提高了系统的可扩展性和可维护性。

建议收藏备用,划走就再也找不到了。

依赖倒置是良好架构设计的基石,社/校招面试也常问,必知必会非常重要。


请使用浏览器的分享功能分享到微信等