什么叫做依赖倒置

前两天小组里面开周会,有一个议题就是大家举例来谈谈对设计原则的理解(SOLID原则),第一个举例的同学谈到的就是依赖倒置原则,他的例子如下:


依赖倒置

上面的例子左边的类显示的是Person类依赖了具体的工具,例如Person中有一个方法drive(Car),这样Person就对具体的交通工具产生了依赖,如果这个时候想要使用其它的交通工具如Bike,Bus,就需要修改Person类。因此将原来的设计修改成了右边的样子,引入了抽象接口Transportation(交通工具),这样Person只需要依赖交通工具即可,至于到底选择何种工具,就交给IOC容器,进行依赖注入即可。

看完了例子我们再来看看依赖倒置的定义

高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。

高层模块不应该依赖底层模块

这句话是依赖倒置的核心。指的是概念的自包含,上层模块不应该去依赖具体的某个底层提供方。在设计系统时,常常会采用分层的方式:数据访问层,业务逻辑层,展示层等,而每一层会依赖下层的API,这样导致一个问题就是难以替换下层的提供方。那应该如何做呢,运用概念完整性,业务逻辑层需要有自己的数据访问规范,也就是SPI,然后数据提供层去适配这个SPI,这样如果替换了下层的数据层之后对业务逻辑层也不会产生影响。再比如电脑的主板不会去依赖显卡,内存,而是通过自己定义的扩展槽来实现,每个不同的硬件来适配扩展槽的标准。

前些天在内网看到一篇文章,里面有一句话很好

我家孩子跟我姓,你家孩子跟你姓,接口谁家的跟谁姓。

这句话的说的是接口所有权的归属问题。例如人吃巧克力

public interface IChocolates{}

public class Oreo implements IChocolates{}

public class Dove implements IChocolates{}

public interface Person {    void eat(IChocolates chocolates);}

上面的例子中人对巧克力产生了依赖,那人吃的行为依赖其实跟巧克力没有关系,在巧克力出现之前就已经存在了,因此吃的动作依赖的接口应该是人本身内部的概念,这个接口的归属权应该属于人,概念应该为可食用的(edible)。因此人对巧克力的依赖关系应该倒置为巧克力对可食用接口的依赖。这样倒置之后对人来说具有了更好的扩展性,不仅可以吃各种不同的巧克力,还可以吃饼干,米饭,鱼肉等等其它任何可吃的东西。

public interface IChocolates extends IEdible{}

public class Oreo implements IChocolates {}

public class Dove implements IChocolates {}

public interface Person { void eat( IEdible  edible); }

抽象不应该依赖细节;细节应该依赖抽象

有了上面的概念之后,自然也就有了依赖抽象而非细节,因为上层制定的是SPI(Service Provider Interface), 也就是一种通用的实现接口,由不同的实现方来提供实现。另外一般实现方也会提供自己的API接口供其他应用来使用,因此一般实现方会在自己的API上面封装一层SPI的适配层来提供给上层使用。

同时依赖抽象而非细节也是依赖注入与IOC实现的基础。

小结

因此,依赖倒置除了我们常常说道的依赖注入,依赖抽象之外更重要的是概念的归属问题,在使用API的时候要去思考将要依赖的概念到底是属于谁的,到底应该谁依赖谁,为接口找到真正的归属。

其它

我们再回头来看看第一个例子,人对交通工具有依赖,那交通工具里面的方法呢,应该有一个运输的方法(transport),那方法的参数呢,运输的是什么呢,肯定不能是人,因为还可以运输货物,交通工具对人是没有依赖的,这里应该是可运输的(ITransportable), Person应该去实现该接口,这样对交通工具就是一个完整的概念了,而且更具有扩展性。

推荐阅读更多精彩内容