加载中…
个人资料
dawnstar1
dawnstar1
  • 博客等级:
  • 博客积分:0
  • 博客访问:195,035
  • 关注人气:11
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
相关博文
推荐博文
谁看过这篇博文
加载中…
正文 字体大小:

【转】面向对象的3个基本要素和5个基本设计原则2

(2014-06-03 15:21:27)
分类: OOD
一、LSP简介(LSP--Liskov Substitution Principle):
定义:如果对于类型S的每一个对象o1,都有一个类型T的对象o2,使对于任意用类型T定义的程序P,将o2替换为o1,P的行为保持不变,则称S为T的一个子类型。
子类型必须能够替换它的基类型。LSP又称里氏替换原则。
对于这个原则,通俗一些的理解就是,父类的方法都要在子类中实现或者重写。

二、举例说明:
对于依赖倒置原则,说的是父类不能依赖子类,它们都要依赖抽象类。这种依赖是我们实现代码扩展和运行期内绑定(多态)的基础。因为一旦类的使用者依赖某个具体的类,那么对该依赖的扩展就无从谈起;而依赖某个抽象类,则只要实现了该抽象类的子类,都可以被类的使用者使用,从而实现了系统的扩展。
但是,光有依赖倒置原则,并不一定就使我们的代码真正具有良好的扩展性和运行期内绑定。请看下面的代码:
Java代码  收藏代码
  1. public class Animal  
  2.  
  3.     private string name;  
  4.     public Animal(string name)  
  5.      
  6.         this.name name;  
  7.      
  8.     public void Description()  
  9.      
  10.         Console.WriteLine("This is a(an) " name);  
  11.      
  12.  
  13.    
  14. //下面是它的子类猫类:  
  15. public class Cat Animal  
  16.  
  17.     public Cat(string name)  
  18.      
  19.           
  20.      
  21.     public void Mew()  
  22.      
  23.         Console.WriteLine("The cat is saying like 'mew'");  
  24.      
  25.  
  26.    
  27. //下面是它的子类狗类:  
  28. public class Dog Animal  
  29.  
  30.     public Dog(string name)  
  31.      
  32.    
  33.      
  34.     public void Bark()  
  35.      
  36.         Console.WriteLine("The dog is saying like 'bark'");  
  37.      
  38.  
  39.    
  40. //最后,我们来看客户端的调用:  
  41. public void DecriptionTheAnimal(Animal animal)  
  42.  
  43.     if (typeof(animal) is Cat)  
  44.      
  45.         Cat cat (Cat)animal;  
  46.         Cat.Decription();  
  47.         Cat.Mew();  
  48.      
  49.     else if (typeof(animal) is Dog)  
  50.      
  51.         Dog dog (Dog)animal;  
  52.         Dog.Decription();  
  53.         Dog.Bark();  
  54.      
  55.  

通过上面的代码,我们可以看到虽然客户端的依赖是对抽象的依赖,但依然这个设计的扩展性不好,运行期绑定没有实现。
是什么原因呢?其实就是因为不满足里氏替换原则,子类如Cat有Mew()方法父类根本没有,Dog类有Bark()方法父类也没有,两个子类都不能替换父类。这样导致了系统的扩展性不好和没有实现运行期内绑定。
现在看来,一个系统或子系统要拥有良好的扩展性和实现运行期内绑定,有两个必要条件:第一是依赖倒置原则;第二是里氏替换原则。这两个原则缺一不可。

我们知道,在我们的大多数的模式中,我们都有一个共同的接口,然后子类和扩展类都去实现该接口。
下面是一段原始代码:
if(action.Equals(“add”))
Java代码  收藏代码
  1.  
  2.   //do add action  
  3.  
  4. else if(action.Equals(“view”))  
  5.  
  6.   //do view action  
  7.  
  8. else if(action.Equals(“delete”))  
  9.  
  10.   //do delete action  
  11.  
  12. else if(action.Equals(“modify”))  
  13.  
  14.   //do modify action  
  15.  

我们首先想到的是把这些动作分离出来,就可能写出如下的代码:
Java代码  收藏代码
  1. public class AddAction  
  2.  
  3.     public void add()  
  4.      
  5.         //do add action  
  6.      
  7.  
  8. public class ViewAction  
  9.  
  10.     public void view()  
  11.      
  12.         //do view action  
  13.      
  14.  
  15. public class deleteAction  
  16.  
  17.     public void delete()  
  18.      
  19.         //do delete action  
  20.      
  21.  
  22. public class ModifyAction  
  23.  
  24.     public void modify()  
  25.      
  26.         //do modify action  
  27.      
  28.  

我们可以看到,这样代码将各个行为独立出来,满足了单一职责原则,但这远远不够,因为它不满足依赖颠倒原则和里氏替换原则。
下面我们来看看命令模式对该问题的解决方法:
Java代码  收藏代码
  1. public interface Action  
  2.  
  3.     public void doAction();  
  4.  
  5. //然后是各个实现:  
  6. public class AddAction Action  
  7.  
  8.     public void doAction()  
  9.      
  10.         //do add action  
  11.      
  12.  
  13. public class ViewAction Action  
  14.  
  15.     public void doAction()  
  16.      
  17.         //do view action  
  18.      
  19.  
  20. public class deleteAction Action  
  21.  
  22.     public void doAction()  
  23.      
  24.         //do delete action  
  25.      
  26.  
  27. public class ModifyAction Action  
  28.  
  29.     public void doAction()  
  30.      
  31.         //do modify action  
  32.      
  33.  
  34. //这样,客户端的调用大概如下:  
  35. public void execute(Action action)  
  36.  
  37.     action.doAction();  
  38.  


看,上面的客户端代码再也没有出现过typeof这样的语句,扩展性良好,也有了运行期内绑定的优点。

三、LSP优点:
1、保证系统或子系统有良好的扩展性。只有子类能够完全替换父类,才能保证系统或子系统在运行期内识别子类就可以了,因而使得系统或子系统有了良好的扩展性。
2、实现运行期内绑定,即保证了面向对象多态性的顺利进行。这节省了大量的代码重复或冗余。避免了类似instanceof这样的语句,或者getClass()这样的语句,这些语句是面向对象所忌讳的。
3、有利于实现契约式编程。契约式编程有利于系统的分析和设计,指我们在分析和设计的时候,定义好系统的接口,然后再编码的时候实现这些接口即可。在父类里定义好子类需要实现的功能,而子类只要实现这些功能即可。

四、使用LSP注意点:
1、此原则和OCP的作用有点类似,其实这些面向对象的基本原则就2条:1:面向接口编程,而不是面向实现;2:用组合而不主张用继承
2、LSP是保证OCP的重要原则
3、这些基本的原则在实现方法上也有个共同层次,就是使用中间接口层,以此来达到类对象的低偶合,也就是抽象偶合!
4、派生类的退化函数:派生类的某些函数退化(变得没有用处),Base的使用者不知道不能调用f,会导致替换违规。在派生类中存在退化函数并不总是表示违反了LSP,但是当存在这种情况时,应该引起注意。 
5、从派生类抛出异常:如果在派生类的方法中添加了其基类不会抛出的异常。如果基类的使用者不期望这些异常,那么把他们添加到派生类的方法中就可以能会导致不可替换性。

 

一、DIP简介(DIP--Dependency Inversion Principle):
1、高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
2、抽象不应该依赖于细节,细节应该依赖于抽象。

高层模块包含了一个应该程序中的重要的策略选择和业务模型,正是这些高层模块才使得其所有的应用程序区别于其他,如果高层依赖于低层,那么对低层模块的改动就会直接影响到高层模块,从而迫使它们依次做出改动。

二、举例说明:
反面例子:

缺点:耦合太紧密,Light发生变化将影响ToggleSwitch。

解决办法一:
将Light作成Abstract,然后具体类继承自Light。

优点:ToggleSwitch依赖于抽象类Light,具有更高的稳定性,而BulbLight与TubeLight继承自Light,可以根据"开放-封闭"原则进行扩展。只要Light不发生变化,BulbLight与TubeLight的变化就不会波及ToggleSwitch。
缺点:如果用ToggleSwitch控制一台电视就很困难了。总不能让TV继承自Light吧。

解决方法二:

优点:更为通用、更为稳定。

三、DIP优点:
使用传统过程化程序设计所创建的依赖关系,策略依赖于细节,这是糟糕的,因为策略受到细节改变的影响。依赖倒置原则使细节和策略都依赖于抽象,抽象的稳定性决定了系统的稳定性。

四、启发式规则:
1、任何变量都不应该持有一个指向具体类的指针或者引用
2、任何类都不应该从具体类派生(始于抽象,来自具体)
3、任何方法都不应该覆写它的任何基类中的已经实现了的方法


一、ISP简介(ISP--Interface Segregation Principle):
使用多个专门的接口比使用单一的总接口要好。
一个类对另外一个类的依赖性应当是建立在最小的接口上的。
一个接口代表一个角色,不应当将不同的角色都交给一个接口。没有关系的接口合并在一起,形成一个臃肿的大接口,这是对角色和接口的污染。

“不应该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构。”这个说得很明白了,再通俗点说,不要强迫客户使用它们不用的方法,如果强迫用户使用它们不使用的方法,那么这些客户就会面临由于这些不使用的方法的改变所带来的改变。

二、举例说明:
参考下图的设计,在这个设计里,取款、存款、转帐都使用一个通用界面接口,也就是说,每一个类都被强迫依赖了另两个类的接口方法,那么每个类有可能因为另外两个类的方法(跟自己无关)而被影响。拿取款来说,它根本不关心“存款操作”和“转帐操作”,可是它却要受到这两个方法的变化的影响。

那么我们该如何解决这个问题呢?参考下图的设计,为每个类都单独设计专门的操作接口,使得它们只依赖于它们关系的方法,这样就不会互相影了!

三、实现方法:
1、使用委托分离接口
2、使用多重继承分离接口

    以上就是5个基本的面向对象设计原则,它们就像面向对象程序设计中的金科玉律,遵守它们可以使我们的代码更加鲜活,易于复用,易于拓展,灵活优雅。不同的设计模式对应不同的需求,而设计原则则代表永恒的灵魂,需要在实践中时时刻刻地遵守。就如ARTHUR J.RIEL在那边《OOD启示录》中所说的:“你并不必严格遵守这些原则,违背它们也不会被处以宗教刑罚。但你应当把这些原则看做警铃,若违背了其中的一条,那么警铃就会响起。”

面向对象设计原则 http://www.cnblogs.com/feipeng/archive/2007/03/02/661840.html

0

阅读 评论 收藏 转载 喜欢 打印举报/Report
  • 评论加载中,请稍候...
发评论

    发评论

    以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

      

    新浪BLOG意见反馈留言板 电话:4000520066 提示音后按1键(按当地市话标准计费) 欢迎批评指正

    新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 会员注册 | 产品答疑

    新浪公司 版权所有