面向对象设计的基本原则

参考
http://www.cnblogs.com/cavingdeep/archive/2004/10/28/208956.html
http://www.cnblogs.com/seacryfly/archive/2011/12/29/seacryfly.html
http://www.cnblogs.com/sunflower627/p/4718702.html





    OOD基本上以下几大原则,而实际上都是互补的,也就是说一些原则需要利用另一些原则来实现自己。原则如下:
    1) Open-Close Principle(OCP),开-闭原则
    讲的是设计要对扩展有好的支持,而对修改要严格限制。这是最重要也是最为抽象的原则,基本上我们所说的Reusable Software既是基于此原则而开发的。其他的原则也是对它的实现提供了路径。


    •既开放又封闭,对扩展是开放的,对更改是封闭的!
    •扩展即扩展现行的模块,当我们软件的实际应用发生改变时,出现新的需求,就需要我们对模块进行扩展,使其能够满足新的需求!

    更改封闭即是在我们对模块进行扩展时,勿需对源有程序代码和DLL进行修改或重新编译文件!
    这个原则对我们在设计类的时候很有帮助,坚持这个原则就必须尽量考虑接口封装,抽象机制和多态技术



因为:
    开放封闭原则主要体现在对扩展开放、对修改封闭,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。软件需求总是变化的,世界上没有一个软件的是不变的,因此对软件设计人员来说,必须在不需要对原有系统进行修改的情况下,实现灵活的系统扩展。

所以:
    可以通过Template Method模式和Strategy模式进行重构,实现对修改封闭,对扩展开放的设计思路。
    封装变化,是实现开放封闭原则的重要手段,对于经常发生变化的状态,一般将其封装为一个抽象,拒绝滥用抽象,只将经常变化的部分进行抽象。


    2) Liskov Substituition Principle(LSP),里氏代换原则
    很严格的原则,规则是“子类必须能够替换基类,否则不应当设计为其子类。”也就是说,子类只能去扩展基类,而不是隐藏或覆盖基类,如有这方面需要的设计就应当参考以下两种方法替换:
   1.

   2.



    •子类可以替换父类并且出现在父类能够出现的任何地方
    •这个原则也是在贯彻GOF倡导的面向接口编程!
    在这个原则中父类应尽可能使用接口或者抽象类来实现!

    子类通过实现了父类接口,能够替父类的使用地方!
    通过这个原则,我们客户端在使用父类接口的时候,通过子类实现!
    意思就是说我们依赖父类接口,在客户端声明一个父类接口,通过其子类来实现
    这个时候就要求子类必须能够替换父类所出现的任何地方,这样做的好处就是,在根据新要求扩展父类接口的新子类的时候而不影响当前客户端的使用!



因为:
    里氏替换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。里氏替换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。

所以:
    使用里氏替换原则时需要注意,子类的所有方法必须在父类中声明,或子类必须实现父类中声明的所有方法。尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。

    从大局看Java的多态就属于这个原则。


    3) Dependence Inversion Principle(DIP),依赖倒换原则
    “设计要依赖于抽象而不是具体化”。换句话说就是设计的时候我们要用抽象来思考,而不是一上来就开始划分我需要哪些哪些类,因为这些是具体。这样做有什么好处呢?人的思维本身实际上就是很抽象的,我们分析问题的时候不是一下子就考虑到细节,而是很抽象的将整个问题都构思出来,所以面向抽象设计是符合人的思维的。另外这个原则会很好的支持OCP,面向抽象的设计使我们能够不必太多依赖于实现,这样扩展就成为了可能,这个原则也是另一篇文章《Design by Contract》的基石。

    •  传统的结构化编程中,最上层的模块通常都要依赖下面的子模块来实现,也
称为高层依赖低层!
    所以DIP原则就是要逆转这种依赖关系,让高层模块不要依赖低层模块,所以称之为依赖倒置原则!



因为:
    具体依赖抽象,上层依赖下层。假设B是较A低的模块,但B需要使用到A的功能,这个时候,B不应当直接使用A中的具体类;而应当由B定义一抽象接口,并由A来实现这个抽象接口,B只使用这个抽象接口;这样就达到了依赖倒置的目的,B也解除了对A的依赖,反过来是A依赖于B定义的抽象接口。通过上层模块难以避免依赖下层模块,假如B也直接依赖A的实现,那么就可能造成循环依赖。

所以:
    采用依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,减少并行开发引起的风险,提高代码的可读性和可维护性。

    从大局看Java的多态就属于这个原则。


    4) Interface Segregation Principle(ISP)接口隔离原则
    “将大的接口打散成多个小接口”,这样做的好处很明显,我不知道有没有必要再继续描述了,为了节省篇幅,实际上我对这些原则只是做了一个小总结,如果有需要更深入了解的话推荐看《Java与模式》,MS MVP的一本巨作!^_^


    •  这个原则的意思是:使用多个专门的接口比使用单个接口要好的多!

    这个我有体会,在我实际编程中,为了减少接口的定义,将许多类似的方法都放在一个接口中,最后发现,维护和实现接口的时候花了太多精力,而接口所定义的操作相当于对客户端的一种承诺,这种承诺当然是越少越好,越精练越好,过多的承诺带来的就是你的大量精力和时间去维护!




因为:
    提供尽可能小的单独接口,而不要提供大的总接口。暴露行为让后面的实现类知道的越少越好。譬如类ProgramMonkey通过接口CodeInterface依赖类CodeC,类ProgramMaster通过接口CodeInterface依赖类CodeAndroid,如果接口CodeInterface对于类ProgramMonkey和类CodeC来说不是最小接口,则类CodeC和类CodeAndroid必须去实现他们不需要的方法。将臃肿的接口CodeInterface拆分为独立的几个接口,类ProgramMonkey和类ProgramMaster分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。

所以:
    建立单一接口,不要建立庞大的接口,尽量细化接口,接口中的方法尽量少。也就是要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。依赖几个专用的接口要比依赖一个综合的接口更灵活。接口是设计时对外部设定的约定,通过分散定义多个接口,可以预防外来变更的扩散,提高系统的灵活性和可维护性。

    从大局来说Java的接口可以实现多继承就是接口隔离原则的基础保障。


     5) Composition/Aggregation Reuse Principle(CARP)组合/聚合复用原则
    设计者首先应当考虑复合/聚合,而不是继承(因为它很直观,第一印象就是“哦,这个就是OO啊”)。这个就是所谓的“Favor Composition over Inheritance”,在实践中复合/聚合会带来比继承更大的利益,所以要优先考虑。

因为:
    其实整个设计模式就是在讲如何类与类之间的组合/聚合。在一个新的对象里面通过关联关系(包括组合关系和聚合关系)使用一些已有的对象,使之成为新对象的一部分,新对象通过委派调用已有对象的方法达到复用其已有功能的目的。也就是,要尽量使用类的合成复用,尽量不要使用继承。
    如果为了复用,便使用继承的方式将两个不相干的类联系在一起,违反里氏代换原则,哪是生搬硬套,忽略了继承了缺点。继承复用破坏数据封装性,将基类的实现细节全部暴露给了派生类,基类的内部细节常常对派生类是透明的,白箱复用;虽然简单,但不安全,不能在程序的运行过程中随便改变;基类的实现发生了改变,派生类的实现也不得不改变;从基类继承而来的派生类是静态的,不可能在运行时间内发生改变,因此没有足够的灵活性。

所以:
     组合/聚合复用原则可以使系统更加灵活,类与类之间的耦合度降低,一个类的变化对其他类造成的影响相对较少,因此一般首选使用组合/聚合来实现复用;其次才考虑继承,在使用继承时,需要严格遵循里氏代换原则,有效使用继承会有助于对问题的理解,降低复杂度,而滥用继承反而会增加系统构建和维护的难度以及系统的复杂度,因此需要慎重使用继承复用。



    6) Law of Demeter or Least Knowlegde Principle(LoD or LKP),迪米特法则或最少知识原则
    这个原则首次在Demeter系统中得到正式运用,所以定义为迪米特法则。它讲的是“一个对象应当尽可能少的去了解其他对象”。也就是又一个关于如何松耦合(Loosely-Coupled)的法则。

因为:
    类与类之间的关系越密切,耦合度也就越来越大,只有尽量降低类与类之间的耦合才符合设计模式;对于被依赖的类来说,无论逻辑多复杂都要尽量封装在类的内部;每个对象都会与其他对象有耦合关系,我们称出现成员变量、方法参数、方法返回值中的类为直接的耦合依赖,而出现在局部变量中的类则不是直接耦合依赖,也就是说,不是直接耦合依赖的类最好不要作为局部变量的形式出现在类的内部。

所以:
    一个对象对另一个对象知道的越少越好,即一个软件实体应当尽可能少的与其他实体发生相互作用,在一个类里能少用多少其他类就少用多少,尤其是局部变量的依赖类,能省略尽量省略。同时如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一方法的话,可以通过第三者转发这个调用。

    从大局来说Android App开发中的多Fragment与依赖的Activity间交互通信遵守了这一法则。


    7 单一职责原则(Single Responsibility Principle)

    •一个类应该仅有一个引起它变化的原因(最简单,最容易理解却最不容易做到的一个设计原则)
    职员类例子:
    比如在职员类里,将工程师、销售人员、销售经理这些情况都放在职员类里考虑,其结果将会非常混乱,在这个假设下,职员类里的每个方法都要if else判断是哪种情况,从类结构上来说将会十分臃肿,并且上述三种的职员类型,不论哪一种发生需求变化,都会改变职员类!这个是大家所不愿意看到的!


因为:
    可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;提高类的可读性,提高系统的可维护性;变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。需要说明的一点是单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则。

所以:
    从大局上看Android中的Paint和Canvas等类都遵守单一职责原则,Paint和Canvas各司其职。


    好了,以上是几大原则(或法则)的介绍,对这些原则的深入研究正是如何得到设计模式的道路。在进行了深入了解后我们就可以开始看看设计模式了,设计模式正是对这些法则的应用,著名的设计模式有四人帮(Gang of Four,GoF)的23个模式,除此之外还有很多其他的一些著名模式,大家可以慢慢研究,如果能自己产出一两个模式的话那就太好了,证明你也是高手了!^_^

猜你喜欢

转载自jacky-dai.iteye.com/blog/2306882