软件设计原则----依赖倒置原则DIPhttp://blog.csdn.net/beyondhaven/article/details/6821091

软件设计原则----依赖倒置原则(DIP)

"要依赖于抽象,不要依赖于具体。”

“要针对接口编程,不要针对实现编程。”

陈述:

  • 高层模块不应该依赖于低层模块。二者应该依赖于抽象。
  • 抽象不应该依赖于细节。细节应该依赖于抽象。
分析:
  • 所谓“倒置”是相对于传统的开发方法(例如结构化方法)中总是倾向于让高层模块依赖于低层模块而言的软件结构而言的。
  • 高层包含应用程序的策略和业务模型,而低层包含更多的实现细节,平台相关细节等。高层依赖低层将导致:
    • 难以复用。通常改变一个软硬件平台将导致一些具体的实现发生变化,如果高层依赖低层,这种变化将导致逐层的更改。
    • 难以维护。低层通常是易变的。
层次化:
  • “……所有良构的OO体系结构都具有清晰的层次定义,每个层次通过一个定义良好的、受控的接口向外提供了一组内聚的服务。”
——Booch
对上述论述可能存在两种不同的理解:
  • 简单的理解


  • 更好的理解
1)依赖关系倒置
下层的实现,依赖于上层的接口
2)接口所有权倒置
客户拥有接口,而服务者则从这些接口派生



我们可以分两种模式来定义:
1、依赖不倒置的开发:自顶向下首先设计整个软件的分解结构,然后首先实现下层的功能,再实现上层的功能,并使上层调用下层函数。
2、依赖倒置的开发:首先设计上层需要调用的接口,并实现上层,然后低层类从上层接口派生,实现低层。在这种情况下, 接口属于上层


实例
我们来看一下Button与Lamp:
Button(开关)感知外界的变化。
当接受到Poll(轮询)消息时,判断其是否被“按下”。这个按下是抽象的(不关心通过什么样的机制去感知):
  • 可能是GUI上的一个按钮被鼠标单击。
  • 可能是一个真正的按钮被手指按下。
  • 可能是一个防盗装置检测到了运动。
  • ……
Lamp(灯)根据指示,收到turn on消息显示某种灯光,收到turn off消息关闭灯光
  • 可能是计算机控制台的LED。
  • 可能是停车场的日光灯。
  • 可能是激光打印机中的激光。
  • ……
应该如何设计程序来用Button控制Lamp呢?

初步设计
Button对象直接依赖Lamp对象,从而:
  • Lamp的任何变化都会影响到Button,导致其改写或者重新编译。
  • 黑盒方式复用Button来控制一个Motor类变得不可能。

  1. classButton{
  2. Lamp*itsLamp;
  3. public:
  4. voidpoll(){
  5. if(/*somecondition*/)
  6. itsLamp->turnOn();
  7. ....
  8. }
  9. };
问题
上面的设计违反了DIP。应用程序的高层策略没有和低层实现分离;抽象没有和具体细节分离。

改进思路
依赖于抽象
什么是高层策略?就是应用背后的抽象----背后的抽象是检测用户的开/关指令:

  • 用什么机制检测用户的指令?无关紧要。
  • 目标对象是什么?无关紧要。
  • 他们不会影响到抽象的具体细节。

改进后的设计:

Button依赖于抽象的接口ButtonServer(向该接口发消息)。ButtonServer提供一些抽象的方法,Button类通过这些接口可以开启或关掉一些东西。

Lamp也依赖于ButtonServer接口(从此接口派生),提供具体的实现。

如下图所示:


部分代码:

?
    //Button.h
  1. #include"ButtonServer.h"
  2. classButton{
  3. ButtonServer*bs;
  4. public:
  5. voidpoll();
  6. };
  7. //Button.cpp
  8. voidButton::poll(){
  9. /*mechanismfordetectingturnOncommand*/)
  10. bs->turnOn();
  11. elseif((/*mechanismfordetectingturnOffcommand*/)
  12. bs->turnOff();
  13. }
?
    //ButtonServer.h
  1. classButtonServer{
  2. <spanstyle="white-space:pre"></span>virtualvoidturnOn()=0;
  3. <spanstyle="white-space:pre"></span>voidturnOff()=0;
  4. //lamp.h
  5. classLamp:publicButtonServer{
  6. voidturnOn();
  7. voidturnOff();
  8. //lamp.cpp
  9. voidLamp::turnOn(){
  10. <spanstyle="white-space:pre"></span>/*codesforturnonaspecificdevice*/
  11. voidLamp::turnOff(){
  12. <spanstyle="white-space:pre"></span>/*codesforturnoffaspecificdevice*/
  13. }
分析
上述设计使得Button可以控制所有愿意实现ButtonServer接口的设备,甚至是一个尚未开发出来的设备。

质疑:
这样的设计是不是强加了这样一个约束——所有需要被Button控制的对象一定要实现ButtonServer类。
如果我的设备还希望能够被另一个对象控制,比如Switch控制,怎么办?
这种设计是不是将Button对Lamp的依赖转嫁成了Lamp对Button的依赖呢?(毕竟Lamp只能被一种Button控制也是不好的)

抗辩:
上述质疑不成立。Button依赖于ButtonServer接口,但是接口并不依赖于Button,也就是说任何知道如何操作ButtonServer接口的对象都可以操作Lamp。
也许需要改进的仅仅是ButtonServer这样一个有些“误导性”的名字,我们可以将这个名字该得更加抽象一些,例如:SwitchableDevice

总结

使用传统过程化设计所创造出来的依赖关系结构,策略是依赖于细节的。----策略受细节改变影响
面向对象程序设计倒置了依赖关系结构,使细节和策略都依赖于抽象,成为面向对象设计的标志。
依赖倒置原则是实现面向对象技术的基本低层机制,有利于复用和维护。
依赖倒置原则假定具体类都是会变化的,避免对具体类的直接引用,可能会导致大量的类。功能强大,最不容易实现。

相应设计模式:
Factory Method
Prototype
Iterator

参考资源:

《设计模式:可复用面向对象软件的基础》,ERICH GAMMA RICHARD HELM RALPH JOHNSON JOHN VLISSIDES著作,李英军 马晓星 蔡敏 刘建中译,机械工业出版社,2005.6

《敏捷软件开发:原则、模式与实践》,Robert C. Martin著,邓辉译,清华大学出版社,2003.9

《设计模式解析》,Alan Shalloway等著(徐言声译),人民邮电出版社,2006.10

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结