从零开始学习Java设计模式 | 软件设计原则篇:依赖倒转原则

Posted 李阿昀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零开始学习Java设计模式 | 软件设计原则篇:依赖倒转原则相关的知识,希望对你有一定的参考价值。

在本讲,我将为大家介绍软件设计原则里面的第三个原则,即依赖倒转原则。

概述

什么是依赖倒转原则呢?我们来看一下下面这段描述:

高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

这句话看起来就不好懂,不过没关系,下面我会为大家详细解释下。

你可能会问的第一个问题是高层模块是什么?低层模块又是什么?下面我用一张图来解释一下。

在这里插入图片描述

仔细看完以上类图,相信你对高层模块和低层模块有了一个简单的认识。

继续看上面那句话,高层模块和低层模块两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。这句话又该如何理解呢?其实这里面的细节指的就是具体的实现类或者子类。以上面的例子来说,A类是依赖于B类的,而B类又是一个具体的类,也即不是抽象类或者接口。现在我们的做法就是要把B类向上进行抽取,抽取出来一个抽象类或者接口,那么这样的话,我们在进行依赖的时候,A类就不需要直接依赖于B类了,而是依赖其父类或者父接口就行了,即细节应该依赖于抽象。

在这里插入图片描述

看完以上那句话,如果大家还不是特别理解的话,那么接下来我再通过一个例子来为大家解释一下依赖倒转原则。其实,依赖倒转原则是开闭原则的具体的实现。

案例

这个例子就是组装电脑。

案例分析

现在我们要组装一台电脑,那么就得需要一些配件了,例如cpu、硬盘、内存条。只有这些配件都有了,计算机才能正常的运行。选择cpu的话,它又有很多品牌,比如说Intel、AMD等等,硬盘也是一样,它也有很多品牌,如希捷、西数等等,内存条同样也是,有金士顿、海盗船等等这些品牌。所以,我们在组装电脑的时候,可以选择任意一个品牌的配件来进行组装。

这样,通过对以上案例的描述,相信大家不难画出如下类图,要是真画不出来,那就看下面类图吧!

在这里插入图片描述

从以上类图中可以看到,有一个希捷硬盘类,它里面既有存储数据的方法,也有获取数据的方法;其次,还有一个英特尔cpu类,它里面有一个运行的方法,即run方法;最后还有一个金士顿内存条类,它里面有保存数据的方法。

大家注意了,以上三个类均不能独立运行,而是必须组合到Computer类里面,所以,你会看到在Computer类里面是以成员变量的形式来声明希捷硬盘、英特尔cpu、金士顿内存条等等这些配件的,继而我们就得为这些成员变量提供相应的getter和setter方法了。此外,在Computer类里面还定义了一个run方法,该方法就是用来运行计算机的,当然了,计算机的运行肯定是包含了希捷硬盘、英特尔cpu、金士顿内存条等等这些配件的运行的。

接下来,我们便来通过代码实现以上组装电脑案例。

案例实现

打开咱们的maven工程,然后在com.meimeixia.principles包下创建一个子包,即demo3,接着在com.meimeixia.principles.demo3包下再创建一个子包,即before,我们首次是在该包下来存放咱们编写的代码的。接下来,我们就要正式开始编写代码来实现以上案例了。

首先,在com.meimeixia.principles.demo3.before包下新建第一个类,即希捷硬盘类。

package com.meimeixia.principles.demo3.before;

/**
 * 希捷硬盘类
 * @author liayun
 * @create 2021-05-27 19:28
 */
public class XiJieHardDisk {

    // 存储数据的方法
    public void save(String data) {
        System.out.println("使用希捷硬盘存储数据为:" + data);
    }

    // 获取数据的方法
    public String get() {
        System.out.println("使用希捷硬盘获取数据");
        return "数据";
    }

}

然后,新建第二个类,即InterCpu。

package com.meimeixia.principles.demo3.before;

/**
 * Intel cpu
 * @author liayun
 * @create 2021-05-27 19:33
 */
public class IntelCpu {

    public void run() {
        System.out.println("使用Intel处理器");
    }

}

接着,新建第三个类,即金士顿内存条类。

package com.meimeixia.principles.demo3.before;

/**
 * 金士顿内存条类
 * @author liayun
 * @create 2021-05-27 19:36
 */
public class KingstonMemory {

    public void save() {
        System.out.println("使用金士顿内存条");
    }

}

至此,以上三个配件类我们就定义完毕了。而且在上面我也说过了,这些组件是不能独立去运行的,所以,我们还需要定义一个Computer类,并将以上三个配件给组合进来。

package com.meimeixia.principles.demo3.before;

/**
 * @author liayun
 * @create 2021-05-27 19:44
 */
public class Computer {

    private XiJieHardDisk hardDisk;
    private IntelCpu cpu;
    private KingstonMemory memory;

    public XiJieHardDisk getHardDisk() {
        return hardDisk;
    }

    public void setHardDisk(XiJieHardDisk hardDisk) {
        this.hardDisk = hardDisk;
    }

    public IntelCpu getCpu() {
        return cpu;
    }

    public void setCpu(IntelCpu cpu) {
        this.cpu = cpu;
    }

    public KingstonMemory getMemory() {
        return memory;
    }

    public void setMemory(KingstonMemory memory) {
        this.memory = memory;
    }

    public void run() {
        System.out.println("运行计算机");
        // 计算机运行时,各个配件应各司其职,干自己的活
        String data = hardDisk.get();
        System.out.println("从硬盘上获取的数据是:" + data);
        cpu.run();
        memory.save();
    }

}

这时,我们就来运行一下以上测试类,看一下计算机能不能正常的去运行,并且使用硬盘、cpu和内存条等等这些配件。从下图所示的打印结果中可以看到,计算机是能正常运行的,并且还伴随着硬盘、cpu和内存条等等这些组件各司其职,做着自己的工作。

在这里插入图片描述

那么你觉得以上代码有没有什么问题啊?从以上代码中可以看到,已经组装了一台计算机,但是似乎组装的计算机的cpu只能是Intel的,内存条只能是金士顿的,硬盘只能是希捷的,为什么呢?因为在Computer类里面成员变量声明的是固定品牌的配件,如果我们后期想换成其他品牌的配件的话,那么是不是还得需要去修改Computer类啊?这就违背开闭原则了。

因此,这对用户肯定是不友好的,用户有了机箱之后,肯定是想按照自己的喜好选择自己喜欢的配件进行组装。

综上所述,以上案例遇到的问题就是,违背了开闭原则,因为后期如果我们想要把cpu换成AMD品牌的,那么我们还得去修改Computer类。

案例改进

虽说我们实现了以上组装电脑的案例,但是我们也看到了该案例所存在的问题,即违反了开闭原则。因此,我们就要对该案例进行改进了,那如何进行改进呢?这里就需要用到依赖倒转原则了。

首先,我们得重新设计类图。那如何设计新的类图呢?根据依赖倒转原则设计,即分别对希捷硬盘类、英特尔Cpu类、金士顿内存条类向上抽取,抽取出一个父接口,完成之后,现在在Computer类里面就不再聚合具体的实现类了,而是改成了聚合抽象接口这种方式。

以上是关于从零开始学习Java设计模式 | 软件设计原则篇:依赖倒转原则的主要内容,如果未能解决你的问题,请参考以下文章

从零开始学习Java设计模式 | 软件设计原则篇:里氏代换原则

从零开始学习Java设计模式 | 软件设计原则篇:里氏代换原则

从零开始学习Java设计模式 | 软件设计原则篇:合成复用原则

从零开始学习Java设计模式 | 软件设计原则篇:合成复用原则

从零开始学习Java设计模式 | 软件设计原则篇:依赖倒转原则

从零开始学习Java设计模式 | 软件设计原则篇:依赖倒转原则