设计模式中的依赖倒置原则(DIP)

  • 定义:

    高层模块不应该依赖于底层模块,两者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖于抽象。是实现开闭原则的基础,其实就是面向接口编程的解释,理解了面向接口编程,也就理解了依赖倒置。

  • 详细描述:

    在引入抽象层后,系统将具有很好的灵活性,在程序中尽量使用抽象层进行编程,而将具体类写在配置文件中,这样一来,如果系统行为发生变化,只需要对抽象层进行扩展,并修改配置文件,而无须修改原有系统的源代码,在不修改的情况下来扩展系统的功能,满足开闭原则的要求。

    在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

  • 问题由来

    某天有个需求,客户要求开发一个高压锅,可以煮饭,小白开发用了3分钟就设计一个高压锅煮饭的程序,代码如下

    //大米
    class Rice {
    	public String heating() {
    		return "煮大米,加热10分钟就好";
    	}
    }
    
    //高压锅
    class Pot {
    	public void cook(Rice rice) {
    		System.out.println("高压锅开始煮饭...");
    		System.out.println(rice.heating());
    	}
    }
    
    public class Demo1 {
    	public static void main(String[] args) {
    		Pot pot = new Pot();
    		pot.cook(new Rice());
    	}
    }
    
    运行结果:
    高压锅开始煮饭...
    煮大米,加热10分钟就好
    

    大米客户可以自己准备,时间客户也能够跳转,上线后程序运行得非常愉快,从未出现过bug,老板也夸奖了该小白开发,但是有一天,来了一个需求:老板说,要求该高压锅还能炒菜,还需要支持炖排骨汤,小白瞬间就傻了,因为目前的程序办不到

    //炒菜、炖汤的代码如下:
    //炒菜
    class Fry {
    	public String heating() {
    		return "炒菜,加热5分钟就好";
    	}
    }
    
    //炖汤
    class Soup {
    	public String heating() {
    		return "炖汤,加热20分钟就好";
    	}
    }
    

    该高压锅对客户称是全能高压锅,竟然不能炒菜和炖汤,若要支持该功能,还需要把锅的构成给修改下,这显然不行,锅已经卖个客户了,再回来加工显然不合适。这肯定不是一个好的设计,原因就是锅与米饭的耦合太高了,必须降低它们之间的耦合才行。

  • 问题解决:

    我们引入一个抽象层(接口或抽象类),IFood(食物),只要是食物,高压锅都支持

    interface IFood{
    	public String heating();
    }
    

    高压锅与IFood进行依赖,而米饭、菜、汤都属于食物,它们各自去实现IFood接口,这样就符合依赖倒置原则了,修改后的代码如下:

    interface IFood{
    	public String heating();
    }
    
    //炖汤
    class Soup implements IFood{
    	public String heating() {
    		return "炖汤,加热20分钟就好";
    	}
    }
    
    //炒菜
    class Fry implements IFood {
    	public String heating() {
    		return "炒菜,加热5分钟就好";
    	}
    }
    
    //大米
    class Rice implements IFood {
    	public String heating() {
    		return "煮大米,加热10分钟就好";
    	}
    }
    
    //高压锅
    class Pot {
    	public void cook(IFood food) {
    		System.out.println("高压锅开始运作...");
    		System.out.println(food.heating());
    	}
    }
    
    public class Client {
    	public static void main(String[] args) {
    		Pot pot = new Pot();
    		pot.cook(new Rice());
    		pot.cook(new Soup());
    	}
    }
    

    这样修改后,只要是煮食物,我们都不再给高压锅加工,客户也不会让锅回厂重新造了,至于煮什么时候让客户自行准备就可以

  • 优点

    采用依赖倒置原则给多人并行开发带来了极大的便利,比如上例中,原本Pot类与Rice类直接耦合时,Pot类必须等Rice类编码完成后才可以进行编码,因为Pot类依赖于Rice类。修改后的程序则可以同时开工,互不影响,因为Pot与Rice类一点关系也没有。参与协作开发的人越多、项目越庞大,采用依赖导致原则的意义就越重大。

    开闭原则是目标,里氏代换原则是基础,依赖倒转原则是手段

  • 依赖注入(DependencyInjection, DI)

    针对抽象编程时,我们要将具体的对象传递到方法中使用,这种传递我们叫做依赖注入。依赖注入是指当一个对象要与其他对象发生依赖关系时,通过抽象来注入所依赖的对象。常用的注入方式有三种,

    构造注入:构造注入是指通过构造函数来传入具体类的对象

    设值注入:设值注入是指通过Setter方法来传入具体类的对象

    接口注入:接口注入是指通过在接口中声明的业务方法来传入具体类的对象。这些方法在定义时使用的是抽象类型,在运行时再传入具体类型的对象,由子类对象来覆盖父类对象。

  • 编程建议

    在实际编程中,我们一般需要做到如下3点:

    • 低层模块尽量都要有抽象类或接口,或者两者都有。
    • 变量的声明类型尽量是抽象类或接口。
    • 使用继承时遵循里氏替换原则。

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