依赖反转(倒置)原则(Dependency inversion principle,DIP)是面向对象编程6大原则之一。这个原则应该是计算机开发人员必知必会的一个基本原则,但是看了网上很多Blog上贴的解释(讲解、理解等等),发现很多人对该原则的理解还是存在一定偏差的。
首先是该原则的含义,下列引用内容来自于维基百科的依赖反转原则词条:
该原则指一种特定的解耦(传统的依赖关系建立在高层次上,而具体的策略设置则应用在低层次的模块上)形式,使得高层次的模块不依赖于低层次的模块的实现细节,依赖关系被颠倒(反转),从而使得低层次模块依赖于高层次模块的需求抽象。该原则规定:
- 高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口;
- 抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。
网上不少的Blog都有说DIP原则,例子也是举了若干,并且从举例看貌似也很贴切。不过,这些Blog中基本都是在解释依赖接口编程如何如何的好,当然在他们举的例子中均能体现出依赖接口编程的好处,这点本人没有任何的异议,甚至Blog的行文和案例方面都没有可挑剔的,比本人强太多了。但是,DIP中的倒置(习惯使用倒置,不习惯反转,以后均使用倒置)如何体现?难道依赖接口,不依赖具体实现就是所谓的“倒置”?显然这里面还有“事儿”没有说出来。看了网上很多的解释都让人很失望,特别是在搜索引擎的第一页给出的各个“文章”中,只有维基百科里面的解释本人觉得算比较明确的。下面就说说所谓的“倒置”问题谈谈我自己的认识。
个人感觉针对依赖倒置原则,应该是在涉及到软件系统的结构设计及实现时才能更好的去理解。按现在软件开发的“行规”,一个复杂的系统必然存在高层与低层,并且,高层对象使用低层对象为其提供的“服务”来实现自己的业务逻辑,即所谓的高层依赖低层。但如果高层对象直接使用这些低层对象(的具体实现),当业务变化,而低层的已有实现无法满足高层的服务需求时,那就需要“伤筋动骨”。为了避免这样的问题,人们提出了面向(依赖)接口编程,这样只要接口不发生变化,低层的实现不会影响上层的使用。目前网上大多数的Blog基本都说到这里就“完结撒花”了。他们大概、或许、应该是认为依赖了接口就是“倒置”了。可是细想一下,如果仅仅是依赖了接口就“撒花”,那为啥这个原则不叫“依赖接口原则”?这其中的“倒置”究竟是说啥?
按照常理,软件划分层也好,模块也厚,通常都是将相同语义的元素放在一起,因此接口与其实现(类)应该处于一层或模块之中。如下图所示(StarUML绘制,请忽略图片中背景水印,免费的还要啥自行车?):这看似好像没有问题,但是软件的高层应用可能会发生变化,即来自客户的需求会发生变化(这是常事儿啊)。当高层的应用发生了改变,那它依赖的低层对象所提供的服务也很可能要发生变化,因为高层要完成新的业务,低层要负责提供对应的服务。那么问题来了,谁来约定这个接口提供什么样的服务?按照前面的逻辑,接口和实现放在低层中,那应该由低层提供,可是低层开发人员并不负责高层的应用逻辑。低层应该应该只关心自己那点事儿,即负责响应高层的需求,去按照需求提供实现服务。但现在接口放在低层维护,就应该由低层的开发人员负责体现需求的接口的“变更”(提供新的服务)。这样会发现这样的情况,即负责高层实现的开发人员,他们拥有需求,但无法定义描述需求的接口;负责低层的开发人员,他们不管需求,只应提供具体实现,却要维护和应用需求有关的接口。这不就出现了很大的矛盾吗?
因此,为了解决这样的矛盾,人们提出将本应放在低层的接口放在高层,低层的实现依赖高层提供的接口,去实现相应的服务(请参见上图所示)。本人认为这才是DIP中“倒置”的真正含义所在。
本文分两天抽空写的,前一天还能不翻墙直接访问中文的维基百科,今天不翻墙就不行了,白高兴了半天。
ps:想吐槽,已无力,就这么着吧~