设计原则 - 依赖倒转原则

依赖倒转原则: 面向接口编程,而不是面向实现类

含义

  • 依赖倒置原则(Dependence Inversion Principle,DIP)是指设计代码结构时,高层(调用层)模块不应该依赖底层(被调用层)模块,二者都应该依赖其抽象
  • 程序依赖接口,接口定义标准和规范,具体的实现按照场景而不是使用对象硬编码的方式实现
  • 抽象不应该依赖细节,细节应该依赖抽象。即面向接口编程,不要面向实现编程
  • 依赖倒置原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构要比以细节为基础的架构要稳定的多
  • 在Java中,抽象指的是接口或者抽象类,细节就是具体的实现类

通俗点说:要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

核心思想

  • 面向接口编程,不要面向实现编程

优点

  • 减少类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并能够降低修改程序所造成的风险

实现方式

  • 传递依赖关系有三种方式:接口传递(方法注入)、构造方法传递、setter方法传递
  • 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
  • 低层模板尽量都要有抽象类或接口,或者两个都有,程序稳定性更好
  • 变量的声明类型尽量都是抽象类或接口,这样我们的变量变量引用和实际对象间就存在一个缓冲层,有利于程序的扩展和优化
  • 继承时遵循里氏替换原则

案例

组装电脑

类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
解决方案:将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。

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

在这里插入图片描述

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

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

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

代码

public class Computer {
    
    
    private InterCpu interCpu;
    private KingstonMemory kingstonMemory;
    private XiJieHardDisk xiJieHardDisk;

    public InterCpu getInterCpu() {
    
    
        return interCpu;
    }

    public void setInterCpu(InterCpu interCpu) {
    
    
        this.interCpu = interCpu;
    }

    public KingstonMemory getKingstonMemory() {
    
    
        return kingstonMemory;
    }

    public void setKingstonMemory(KingstonMemory kingstonMemory) {
    
    
        this.kingstonMemory = kingstonMemory;
    }

    public XiJieHardDisk getXiJieHardDisk() {
    
    
        return xiJieHardDisk;
    }

    public void setXiJieHardDisk(XiJieHardDisk xiJieHardDisk) {
    
    
        this.xiJieHardDisk = xiJieHardDisk;
    }

    public void run() {
    
    
        System.out.println("运行计算机");
        // 计算机运行时,各个配件应各司其职,干自己的活
        String data = xiJieHardDisk.get();
        System.out.println("从硬盘上获取的数据是:" + data);
        interCpu.run();
        KingstonMemory.save();
    }
}
public class InterCpu {
    
    
    public void run(){
    
    
        System.out.println("使用Intel处理器");
    }
}
public class KingstonMemory {
    
    
    public static void save() {
    
    
        System.out.println("使用金士顿内存条");
    }
}
public class XiJieHardDisk {
    
    
    public void save(String data){
    
    
        System.out.println("使用希捷硬盘保存数据:"+data);
    }

    public String get(){
    
    
        System.out.println("获取希捷硬盘中的数据");
        return "希捷硬盘中的数据";
    }
}

Main

public static void main(String[] args) {
    
    
    // 创建配件
    InterCpu interCpu = new InterCpu();
    KingstonMemory kingstonMemory = new KingstonMemory();
    XiJieHardDisk xiJieHardDisk = new XiJieHardDisk();

    // 创建算计
    Computer computer = new Computer();

    // 组装计算机
    computer.setInterCpu(interCpu);
    computer.setKingstonMemory(kingstonMemory);
    computer.setXiJieHardDisk(xiJieHardDisk);

    // 运行
    computer.run();
}

在这里插入图片描述

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

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

案例改进

给每个配件添加一个接口,不同品牌去实现这个接口

在这里插入图片描述

代码

public class Computer {
    
    
    private Cpu cpu;
    private Memory memory;
    private HardDisk hardDisk;

    public Cpu getCpu() {
    
    
        return cpu;
    }

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

    public Memory getMemory() {
    
    
        return memory;
    }

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

    public HardDisk getHardDisk() {
    
    
        return hardDisk;
    }

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

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

    public String get();
}
public interface Memory {
    
    
    public void save();
}
public class InterCpu implements Cpu {
    
    
    @Override
    public void run(){
    
    
        System.out.println("使用Intel处理器");
    }
}
public class XiJieHardDisk implements HardDisk{
    
    
    @Override
    public void save(String data){
    
    
        System.out.println("使用希捷硬盘保存数据:"+data);
    }

    @Override
    public String get(){
    
    
        System.out.println("获取希捷硬盘中的数据");
        return "希捷硬盘中的数据";
    }
}
public class KingstonMemory implements Memory{
    
    
    @Override
    public void save() {
    
    
        System.out.println("使用金士顿内存条");
    }
}

Main

public static void main(String[] args) {
    
    
    // 创建配件
    KingstonMemory kingstonMemory = new KingstonMemory();
    XiJieHardDisk xiJieHardDisk = new XiJieHardDisk();
    // 任意选择CPU
    AMDCpu amdCpu = new AMDCpu();
    InterCpu interCpu = new InterCpu();

    // 创建算计
    Computer computer = new Computer();

    // 组装计算机
    //computer.setCpu(interCpu);
    computer.setCpu(amdCpu);
    computer.setMemory(kingstonMemory);
    computer.setHardDisk(xiJieHardDisk);

    // 运行
    computer.run();
}

当然了,如果后期你想要去换一个AMD品牌的CPU的话,那么只需要去新建一个AMD品牌的CPU类,然后让它去实现CPU接口就可以了,此时,Computer类是不需要进行修改的。接着,你只需要在测试类中创建一个AMD品牌的CPU对象,并将其作为参数进行一个传递,组装到计算机里面即可。这样,就很好地符合了开闭原则。

最后,做一个总结,面向对象的开发很好的解决了以上案例所存在的问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化。这大大降低了客户程序与实现细节的耦合度。

猜你喜欢

转载自blog.csdn.net/qq_42700109/article/details/132838076