什么叫做依赖倒置
前两天小组里面开周会,有一个议题就是大家举例来谈谈对设计原则的理解(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应该去实现该接口,这样对交通工具就是一个完整的概念了,而且更具有扩展性。