文章引自:http://www.cnblogs.com/lihongchao/archive/2008/02/02/OOA_AND_OOD_3.html
OO设计原则-《Head First OOA&D》读书笔记(3)
这是最后一篇了,主要就是一些很实际的面向对象的设计原则,以及一些Coding过程中需要注意的事情。这应该是程序员们最感兴趣的部分了。其实就我的观点,这部分是比较好掌握的,毕竟这些知识你看到了,实践了,也就学会了。相反对于项目初期的准备工作就很难有具体的原则作为参考,因为不同的项目之间的区别太大了,客户的特点,组织结构,沟通方式都是不同的。也就是涉及到更多的交流和协调工作,需要有很强的管理能力,也就是soft skills。一旦前期工作做好了,舞台搭好了,基调定准了,后续的工作就很好开展了。
下边就说说这些快被说烂了的OO的概念,我这里比较介绍的比较简要也不完整,详细的可以去WikiPedia查。
1. 基本概念
· 封装(Encapsulation)
基本的概念就是,把与外界无关的数据和逻辑放在与外界隔绝的环境中,在外界看来依然比较有条理,不至于太复杂,所以也叫做数据隐藏(Data Hiding)。这样可以避免内部的数据和实现被外界修改或者干扰。这里我们就需要有一些OO的直觉,能够识别出有关联的一组数据和方法,然后把它们封装成独立的类。
比较进阶的考虑封装就是,封装变化(Encapsulate what varies)。为什么这么做呢?因为我们要把问题简化,减小可预期的变化对整体系统的影响,增加系统的稳定性和可维护性。因此就要尽可能把要变化的东西限制在足够小的范围内。封装也不能滥用,把任何东西认为会变化,而要进行封装,那会使系统的复杂性增加而没有太多的价值。
· 继承(Inheritance)
继承使得我们可以基于其他已有的类的基础上建立具备相同属性和方法的子类,并可以根据需要改写或增加这些属性和方法。这样实现了代码复用,避免了重复代码的出现,对于变化的要求能够很轻松的应对。使用继承的时候要尽量把具体方法,属性的定义提到高的层次上来。但是继承会造成类数量过多的情况,可以用代理,组合或聚合的方法来使代码更容易维护。
继承常常被用在对虚类或接口的实现上,这样做可以避免模块之间的耦合过于紧密。对于接口和虚类的区别,我们可以用属于Is-A 还是 Has-A的关系来判断。一个使用继承的原则就是,面向接口,而不是面向实现编程(Programming to interface rather than implementation.)做到这点,我们就可以使我们的系统处于松耦合的状态,易于扩展,满足变化需求。当然,最终我们的系统肯定是要构造真正的实体类的,不可能任何地方都不用new的。
· 多态(Polymorphism)
多态实现了一个类的子类可以当作它的父类使用。这样我们能够得到更大的灵活性,设想一下。你的类中使用到了飞机这个类的对象当作参数传入,那么对于直升机,战斗机,波音飞机这些子类就都是可以使用的,而他们的行为确都稍有不同。
2. 设计原则
· OCP(Open Close Principle)
对扩展开放,对修改封闭,这句话也有很多人说了。为何要如此呢?首先,随着外界要求的变化,我们的代码肯定是需要修改的,因此我们必须承认变化的存在。而我们又不想看到我们的代码在不停的变化之中,变得不稳定,影响已有的用户。因此,我们就要保证,所有的修改不能改变已有的功能。也就是不能够,修改已有的方法,属性的实现代码。那怎么做才能满足变化的需求呢?扩展!扩展包括,派生子类,提供满足变化的新的实现。使用组合(Composition)和聚合(Aggregation)可以避免因继承出现过多的类,也能达到对现有功能的扩展的效果。
· DRY(Don’t Repeat Youself)
避免重复代码。出现重复代码或者功能相同的函数,方法并不是仅仅意味着我们多做了一些不必要的工作,而是对于将来的维护都是一个负担,甚至是个风险。因此我们要保证我们的所有的属性,行为,数据只在我们的系统中一个地方定义,实现,而且是最合理的地方。记住,我们不是仅仅为了保持代码简洁。
· SRP(Single Responsibility Principle)
单职责原则。SRP要求我们要使我们的每一片代码片段都要足够简洁,一个方法只做一件事。一个类只有一个职责。换句话说我们的每个类只能有一个导致它变化的原因(One reason to change)。这样做的好处不言而喻,就是能够使我们的类轻松面对变化,而且易于维护,可读性好。
SRP和DRY原则是相辅相成的。SRP保证了每一个类都是职责单一的,而DRY则保证了这个类在系统中只能有一个,任何处理这类信息的需求都会用到这个类。
· LSP(Liskov Substitution Principle)
Liskov替代原则。LSP给了我们一个检验继承关系合理性的一个工具。任何子类都要能够放在使用父类的环境中使用,而且不能出现致命错误。使用LSP检验的结果,会使我们找到很多不适合使用继承的场景。其实也就是我们的子类变化太大了,它和父类的区别已经明显多过他们的相似,我们就要考虑使用其他方法解决这个问题了。比如代理(Delegation),这个可不是C#中的代理,他的意思是不用继承,而是用其他类的实体的引用的方式,来把一些工作分给那个有相似功能的类。这样可以起到继承相同的效果。
· other
其他还有很多的原则的,但是大家通过上边这几个也能够自己发挥出来一些。其实无外乎就是对OO基本概念的延伸。比如依赖倒置原则(DIP),说我们不能让高层模块依赖于底层模块,而要使用抽象,让高层模块依赖于抽象,底层模块实现抽象。其实也就是对继承或多态的扩充,和OCP有类似作用。
3. 设计模式及其它
很多人都喜欢学习设计模式,还要常常挂在嘴上不停念叨以免忘了。真的有这么神奇吗?其实了解了这些OO的概念和原则,你就会发现,这些设计模式都是对于一个特定场景采用OO来进行分析得出的最精简的一种解决办法。这种办法既不是唯一的也不是放之四海而皆准的。当然,我们了解了这些设计模式,当我们遇到这些场景的时候可以毫不费力的拿来使用,可以节约很大的力气。另一方面,这种典型应用场景常常很少,我们实际的项目中常常会有很多的限制,不同的因素,因此我们就不能直接照搬了,而是依照这些OO原则和概念,来活学活用。要记住,设计模式会使你的程序变得复杂,可读性变差。使用设计模式增加扩展性,灵活性的同时是以牺牲简洁性,易读性为代价的。
还是那句话,所有的原则和概念都是死的,都是供人所用的。因此,我们要首先能够识别出哪些是要变化的,哪些是需要抽象的,那些是其他模块不关心的,然后才能使用这些原则。对于那些不会变化的情况,或者是暂时不变的情况大可以就用最简单的方式解决问题。当我们发现有变化出现的时候再考虑这些原则也为时不晚,这叫做Refactoring to pattern, 没有人能够一次把所有问题都找出来。对于Refactoring的方法和时机的问题,我会找机会单独讨论。