Design Principles - Dependency Inversion Principle

Dependency Inversion Principle: Program for interfaces, not implementation classes

meaning

  • The dependency inversion principle ( Dependence Inversion Principle, DIP) means that when designing the code structure, the high-level (calling layer) module should not depend on the bottom-level (called layer) module, and both should rely on its abstraction.
  • Programs rely on interfaces, interface definition standards and specifications, and specific implementations are implemented according to scenarios instead of using hard-coded objects.
  • Abstractions should not depend on details; details should depend on abstractions. That is, program for interfaces, not for implementation.
  • The principle of dependency inversion is based on the design concept that abstract things are much more stable than the variability of details. Architectures built on abstractions are much more stable than those based on details.
  • In Java, abstraction refers to interfaces or abstract classes, and details are specific implementation classes.

In layman's terms: It is required to program the abstraction, not the implementation, thus reducing the coupling between the client and the implementation module.

main idea

  • Program for interfaces, not implementation

advantage

  • Reduce the coupling between classes, improve the stability of the system, improve the readability and maintainability of the code, and reduce the risks caused by modifying the program

Method to realize

  • There are three ways to pass dependencies: interface passing (method injection), constructor method passing, and setter method passing
  • The purpose of using interfaces or abstract classes is to formulate specifications without involving any specific operations, leaving the task of showing the details to their implementation classes.
  • Low-level templates should have abstract classes or interfaces, or both, for better program stability.
  • The declared types of variables should be abstract classes or interfaces as much as possible, so that there is a buffer layer between our variable variable references and the actual objects, which is conducive to program expansion and optimization.
  • Follow the Liskov substitution principle when inheriting

Case

assembling computer

Class A directly depends on class B. If you want to change class A to depend on class C, you must modify the code of class A. In this scenario, class A is generally a high-level module responsible for complex business logic; classes B and C are low-level modules responsible for basic atomic operations; if class A is modified, it will bring unnecessary risks to the program.
Solution: Modify class A to depend on interface I. Class B and class C each implement interface I. Class A is indirectly connected to class B or class C through interface I, which will greatly reduce the chance of modifying class A.

Now that we want to assemble a computer, we need some accessories, such as CPU, hard drive, and memory stick. Only when these accessories are present can the computer run normally. When it comes to CPUs, there are many brands, such as Intel, AMD, etc. The same goes for hard drives, which also have many brands, such as Seagate, Western Digital, etc. The same goes for memory sticks, including Kingston, Corsair, etc. Therefore, when we assemble a computer, we can choose accessories from any brand for assembly.

Insert image description here

As can be seen from the above class diagram, there is a Seagate hard disk class, which has methods for storing data and methods for obtaining data; secondly, there is also an Intel CPU class, which has a running method, namely run( ) method; finally there is a Kingston memory stick class, which has methods for saving data.

Note that the above three classes cannot run independently, but must be combined into the Computer class. Therefore, you will see that Seagate hard drives, Intel CPUs, Kingston memory modules, etc. are declared in the form of member variables in the Computer class. accessories, and then we have to provide corresponding getter and setter methods for these member variables. In addition, the Computer class also defines a run() method, which is used to run the computer. Of course, the running of the computer must include the running of accessories such as Seagate hard drives, Intel CPUs, Kingston memory sticks, etc. .

Next, we will implement the above computer assembly case through code.

code

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();
}

Insert image description here

As you can see from the above code, a computer has been assembled, but it seems that the CPU of the assembled computer can only be Intel, the memory stick can only be Kingston, and the hard disk can only be Seagate, because the member variables in the Computer class What is declared is a fixed brand of accessories. If we later want to change to accessories of another brand, we will have to modify the Computer class, which violates the opening and closing principle.

Therefore, this is definitely not user-friendly. After users have a chassis, they definitely want to choose their favorite accessories for assembly according to their own preferences.

Case improvements

Add an interface to each accessory, and different brands implement this interface

Insert image description here

code

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();
}

Of course, if you want to change to an AMD brand CPU later, you only need to create a new AMD brand CPU class and let it implement the CPU interface. At this time, the Computer class does not need to be modified. of. Then, you only need to create an AMD-branded CPU object in the test class, pass it as a parameter, and assemble it into the computer. In this way, the opening and closing principle is well complied with.

Finally, to make a summary, object-oriented development has solved the problems in the above cases very well. Generally, the probability of abstraction change is very small, making the user program dependent on abstraction, and the implementation details also depend on abstraction. Even if the implementation details continue to change, as long as the abstraction remains unchanged, the client program does not need to change. This greatly reduces the coupling of client programs to implementation details.

Guess you like

Origin blog.csdn.net/qq_42700109/article/details/132838076