之前在CodeProject上看到一个老外写的文章,里面提到一句话翻译过来就是:依赖倒置是一种软件设计的原则,控制反转是一种软件设计模式。下面我就说说我对这句话的前半部分的理解。
DIP 的英文全称是:Dependency-Inversion Principles,翻译成中文就是依赖倒置原则。这里借《大话设计模式》里面的描述就是:1).高层模块不应该依赖底层模块,两个都应该依赖抽象。2).抽象不应该依赖细节,细节应该依赖抽象。这个里面谈到了一个词“抽象”,这个词在百度百科里面的解释是“从众多的事物中抽取出共同的、本质性的特征,而舍弃其非本质的特征。”此时,也许你会情不自禁的想到抽象类和接口。我们将抽象类与接口这两个名词去替换上面解释依赖倒置的两句话中,读一读,会有什么感觉?好像是在Jeffrey Richter的神著里面(CLR via C#)还是代码大全2中,我看到这样一句话:接口是对行为的抽象,抽象类是对类的抽象。所以看到这句话我们就知道何时用接口,何时用抽象类了(ps:相信好多人刚出来面试的时候会被经常问到‘抽象类与接口的区别’)。
在博客园里面有位老兄在解释依赖倒置的时候,举了驾驶汽车的例子,也有人举打怪的例子。我觉得他们都是在用重构的手法来说明依赖倒置的,比喻说:
1).张三拿了C照,可以驾驶小车了,现在张三是在开屌丝车锐志。
2).突然有一天,老板对张三说用他的宝马750去机场接一位客户,这个时候张三又可以开宝马了。
3).张三今天有事请假,李四接替张三的工作。
针对上面3个场景,我们可以很简单的用代码体现出来。因为场景是顺序发生的,用代码体现的时候我们可能是实现依赖于实现来做的,最起码我很可能这样做。当有一天,有场景4,5,6...发生时并且原来代码结构没有任何变化,对于有代码洁癖的人来说可能你对你写的代码再也看不下去了,理所当然是这样的。按照上面说到的“接口是对行为的抽象,抽象类是对类的抽象”,我们可以很简单的从场景中分析出“驾驶的行为”,锐志,宝马都是车,那么就有“汽车的抽象类”。这样,不管场景如何变化,我们都可以很好的去扩展了,设计模式里面有个原则就是开闭原则,就是说对修改关闭,对扩展开放,我觉得刚才从场景中总结出来的接口与抽象类也可以很好的印证这一点了。上面引用的例子可以在百度里面搜索“依赖倒置”,然后看出去是来自博客园的,你就能看到那哥们的文章了。
记得在上一家公司,我的大部分工作就是抓取数据,管理SqlServer数据库。有这样一个场景。场景的背景:公司是一家小家电代理的电商企业,在淘宝,拍拍,京东,亚马逊等知名平台上开店,上面的销售数据都是通过它们的开放平台里面提供的接口抓取下来的,抓取下来的数据有的是直接保存在数据库,有的是保存在文本里面(txt)。那么我们来分析上述场景以实用于本主题。
一:粗暴型的解决方案。简单的用类图表示如下:
上图中,TaoBaoDataFactory这个类依赖于左边的数据下载类和右边的数据保存类,真可谓是高类聚高耦合啊,职责分明,紧密耦合在一起。这个周完成了淘宝平台的数据抓取,下周又得开发京东平台的数据抓取,为了偷懒,哥直接将程序改一下,多快。不下几个月,几个平台的开发完成的差不多了,思路大同小异,可谓壮观。说真的,当时我真是这么做的,我也很佩服自己。不过说真的,这种做法行不行了,对于老板老说,他只关注结果,不会去在乎你是怎么实现的,所以作为开发人员应当知道怎么样去和老板沟通。单是从一个开发人员角度来说,我们在将事情完成之后,能不能将代码写得更加好看了,更贴近oo思想,更贴近设计模式了。
二:重构后的解决方案。先上类图:
在上面的类图中,我们可以发现两个抽象的行为,一个是download,一个是savedata。也印证了依赖倒置的原则:1).高层模块不应该依赖底层模块,两个都应该依赖抽象。2).抽象不应该依赖细节,细节应该依赖抽象。这个类图只是为了说明依赖倒置原则,其实在实际情况中我们可以用工厂模式加策略模式去包装上述类图,策略模式的好处是封装了计算的细节,或者用抽象工厂模式包装上述类图,它的好处是延迟子类的初始化。不过我们在仔细看看上述类图,定义了两个接口,所有操作完全与接口打交道,是不是有点面向接口编程的感觉?
从上面的例子演变中,我们从简单粗暴的写法到运用oo的思想去解决问题,是不是觉得思路是越来越清晰。扩展也变得更加容易了,耦合度变得更低了。所以我觉得oo的出现再一定程度上解决了过程化的编程。
提到DIP,那么自然会用到IOC,用到了IOC也就很自然的用到了DI。因对IOC还没有深入的了解,所以这里只是简单的带过IOC。
IOC的英文全称是:Inversion of Control,翻译过来就是控制反转。好莱坞的一句很经典的话,你不用来找我,我会主动找你的。也就是说代码的控制器交由系统控制,而不是代码内部,通过IOC可以消除组件或者模块之间的直接依赖,可以让软件系统的开发更具柔性和扩展性。目前主流的IOC容器有Castle Windsor、微软企业库中的Unity、Spring.NET、StructureMap、Ninject等等。这里有一篇文章介绍这些IOC容器的性能。http://blog.csdn.net/gd2008/article/details/7056944
DI的英文全称是:Dependency Injection 几依赖注入。借用网上的描述是,客户类依赖于服务类的抽象接口,并在运行时根据上下文环境,由其他组件(例如DI容器)实例化具体的服务类实例,将其注入到客户类的运行时环境,实现客户类与服务类实例之间松散 的耦合关系。依赖注入里主要讲的就是几种注入方法,大致分3中,构造函数,属性,方法。这里就不在一一实现了。
到这里,DIP是简单的介绍了一下。DIP是软件设计的一种原则,而IOC,DI则为具体的实践模式。IoC和DI为消除模块或者类之间的耦合关系提供了有效的解决方案,从而保证了依赖于抽象和稳定模块或者类型,也就意味着坚持了DIP原则的大方向。