依赖倒置原则

依赖倒置原则(Dependence Inversion Principle,DIP)是指设计代码结构时,高层模块不应依赖底层模块,两者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。通过依赖倒置,可以减少类与类之间的耦合性,提高系统的稳定性和可维护性,并能够降低修改程序所造成的风险。接下来看一个案例,还是以超市为例,先创建一个类SuperMarket:

package com.framework.dip;

public class SuperMarket {
    public void saleWaterMelon() {
        System.out.println("超市售卖西瓜");
    }

    public void saleBanana() {
        System.out.println("超市售卖香蕉");
    }
}

调用一下:

package com.framework.dip;

public class UnitTest {
    public static void main(String[] args) {
        SuperMarket market = new SuperMarket();
        market.saleBanana();
        market.saleWaterMelon();
    }
}

超市生意好了,可能后面陆陆续续进新的水果售卖。这个时候,业务扩展,我们的代码要从底层到高层(调用层)依次修改。比如在SuperMarket类中增加saleOrange()的方法,在高层也要追加调用。如此一来,系统发布以后,实际上是非常不稳定的,在修改代码的同时也会带来意想不到的风险。接下来我们优化代码,创建一个超市的抽象接口IFruits:

package com.framework.dip;

public interface IFruits {
    void sale();
}

然后写一个Banana类:

package com.framework.dip;

public class Banana implements IFruits{
    @Override
    public void sale() {
        System.out.println("超市卖香蕉...");
    }
}

再实现WaterMelon类:

package com.framework.dip;

public class WaterMelon implements IFruits{
    @Override
    public void sale() {
        System.out.println("超市卖西瓜...");
    }
}

修改SuperMarket类:

package com.framework.dip;

public class SuperMarket {
    public void sale(IFruits fruits) {
        fruits.sale();
    }
}

再来看修改之后的调用:

package com.framework.dip;

import com.framework.openClosed.Watermelon;

public class UnitTest {
    public static void main(String[] args) {

        SuperMarket market = new SuperMarket();
        IFruits fruits = new WaterMelon();
        market.sale(fruits);
        fruits = new Banana();
        market.sale(fruits);
    }
}

这时候再来看代码,超市的水果种类无论怎么增加,对于新的水果,我们只需要新建一个类,通过传参的形式告诉SuperMarket,而不需要修改底层代码。实际上这也是大家非常熟悉的一种方式:依赖注入(DI)。注入的方式还有构造器方式和setter方式。我们先来看构造器注入方式:

package com.framework.dip;

public class SuperMarket {
    private IFruits fruits;

    public SuperMarket(IFruits fruits) {
        this.fruits = fruits;
    }

    public void sale() {
        this.fruits.sale();
    }
}

看调用代码:

package com.framework.dip;

import com.framework.openClosed.Watermelon;

public class UnitTest {
    public static void main(String[] args) {
        SuperMarket market = new SuperMarket(new WaterMelon());
        market.sale();
    }
}

可见,构造器注入方式,每次调用时,都要创建实例。那么,如果SuperMarket是全局单例的,我们就只能选择setter方式注入,继续修改SuperMarket类代码:

package com.framework.dip;

public class SuperMarket {
    private IFruits fruits;

    public void setFruits(IFruits fruits) {
        this.fruits = fruits;
    }

    public void sale() {
        this.fruits.sale();
    }
}

再看调用代码:

package com.framework.dip;

import com.framework.openClosed.Watermelon;

public class UnitTest {
    public static void main(String[] args) {
          SuperMarket market = new SuperMarket();
          market.setFruits(new WaterMelon());
          market.sale();
    }
}

总结

以抽象为基准比以细节为基准搭建起来的架构要稳定得多,因此在拿到需求之后,要面向接口编程,先顶层再细节来设计代码结构。