[设计模式](一):OOP设计原则

    最近在看《Head First Head First Design Pattern》,先看的中文版,看着看着有点疑惑,怎么感觉和我之前的认知不太一样?于是去翻了翻英文原版,才发现是翻译的问题。

    写这系列文章的时候,还参考了其他文章,将一并附在文后。

    由于是第一篇,先来写写设计模式的几大原则,了解这几个原则之后,对之后理解“为什么要这么做”有很大的帮助。


>设计模式的核心思想

    封装变化,降低耦合,是软件设计的核心思想,几乎所有的oop设计模式的根本目的就是为了做到这一点。同时,设计模式的目标还体现在,当需求发生变动时,只要修改最少乃至不修改源代码,只是做扩展即可完成目标,即开扩展闭修改原则(OCP)。


>六(七)大设计原则

1.单一职责原则(Single Responsibility Principle - SRP)

    这个原则的基础内容是:对于一个类,

  • 只有一个引起该类变化的原因;
  • 该类的职责是唯一的,且这个职责是唯一引起其他类变化的原因;

    PS:这个原则由于过于简单,常常被忽略在六大设计原则之外。实际上任何一个OOP程序员,哪怕对设计模式一无所知,写代码时也会默认地遵循这个原则。

    举个小例子,动物类与食物类之间的关系是:动物类的职责是吃掉食物,这个唯一的职责引起食物类中属性的消耗,也就是引起了食物类的变化;那么有人要问,动物(比如人类)不是还负责加工食物吗?那这违不违法SRP原则呢?

    我个人的理解是,不违反。SRP更多的是一种对“专注于做一件事”的极端说法,人类吃食物,可能去去皮、煮熟、添加佐料,但其并没有跨类(包括其子类)进行操作;但如果进行了“分享食物制作心得(菜谱)”的操作,就违背了SRP原则,因为此时该类已经不专注于“消耗食物”这一类事中了,而是开始于其他类进行交互(分享知识)。

    用数字代号来说,若T同时负责的职责有P1和P2,当要进行P1这一件事的修改时,有可能会因为改崩了,导致T本身出故障,进而造成P2失效。解决方法就是基于SRP原则,让T1负责P1,T2负责P2。

    显然,其优点有:

  • 代码复杂度降低、可读性提高、更好维护;
  • 降低代码扩展、修改风险;

    然而,SRP虽说是个人人都会在潜移默化中都会遵循的原则,但由于类的颗粒化加深,职责容易开始扩散。就拿上面的例子来说,动物消耗食物,同时也加工食物,虽然根本目的是消耗食物,并不违背SRP原则,但也为以后的维护造成了隐患,这就是一种职责扩散。

    更好的处理方式,是把加工食物的职责单独归给Cooker。

    SRP原则也不是不能违背,因为要完全遵循SRP原则,势必造成颗粒化过于严重的问题,所以当你的方法:

  • 逻辑足够简单
  • 方法足够少
  • 子类够少或后续关联够少
    可以适当违背SRP原则,毕竟SRP就为了解耦准备的,与其造成代码冗余和影响开发效率,还不如适当违背。

    引用:http://blog.csdn.net/yuanlong_zheng/article/details/7423000

2.里氏代换原则(Liskov Substitution Principle)

     父类F原本的职责是P1,现在因为业务需求,需要将这个职责进行扩展为P=(P1+P2),其中P2应有其子类C中的方法实现。要想使子类C能够顺利完成P的所有职责,就要要求父类F中原本负责P1的方法不能被子类C重写,否则就酱导致P职责中的P1职责被破坏。

     解释:设计原则的目的是解耦合,如果C重写了F的职责,不仅破坏了原有的P1链条;那么对于C未来可能存在的子类D,由于继承了C类中的方法,想要再复原P1,就要继续重写相关的从C中继承而来的P1类方法。一旦上游F发生了变动,比如P1链中的方法被精简,那么对于C、D都要进行修改,这就非常糟糕。

    简单来说,LSP原则就是:

  • 扩展职责时,子类不要去重写父类的非抽象方法;
  • 子类可以实现相应的抽象方法或增加自己的方法;
3.依赖倒转原则(Dependence Inversion Principle)

    对于,DIP原则,我们来讲述一个实例:

    对于底层类B和高层类A,A中的方法依赖于B;当有一天,需要扩展A或修改A,使得A对新的底层类C拥有依赖,若通过修改A代码的方法实现,显然是不合理的;该怎么实现呢?举个例子:

    一个士兵类Soldier,会进行开火操作fire():

class Soldier{
    fun fire(gun:M4A1){
        gun.fire()
    }
}
class M4A1{
    fun fire{
         println("士兵使用M4A1开枪了")
    }
}
    当有一天,给了这名士兵一把AK47,他居然无法开火了,因为他对fire()的认知只停留在M4A1()的阶段,而对AK47()毫无反应。要想让他学会AK47(),就要修改Soldier类的代码,这显然很蠢,那么我们该怎么实现呢?(例子用kotlin代码写成)

interface WeaponAction{
     abstruct fun fire()
}
class M4A1:WeaponAction{
     override fun fire(){
          println("士兵使用M4A1开火了")
     }
}
class AK47:WeaponAction{
     override fun fire(){
          println("士兵使用AK47开火了")
     }
}
//现在,只要扩展相应的武器,继承WeaponAction的接口,就能实现这个接口中的所有方法,对士兵类也无需进行修改。做到了开扩展闭修改。
class Soldier{
     fun fire(gun:WeaponAction){
          gun.fire()
     }
}
    所以DIP原则可以这么总结:

  • 底层模块类尽量拥有上层抽象类或接口;
  • 继承时遵循里氏替换原则;
  • 高层类,不直接对底层模块类进行使用,而是使用相应的接口或抽象类,让底层向上转型。
    DIP原则的一个具体体现,就是 代理模式,也就是上面那个例子中所使用的方法。

4.接口隔离原则(Interface Segregation Principle)
    ISP原则的目的是避免接口过于臃肿,还是举上文那个开枪的例子,M4A1和AK47同时实现接口WeaponAction,因为M4A1和手枪USP是可以加装消音器Silencer的,如果把这个方法也写进这个接口,对于M4A1和USP来说,符合DIP原则,但别忘了AK47是没有消音器的(仅对CS游戏而言),但由于他实现了WeaponAction接口,就要一并实现这个方法,这就违背了ISP原则。

    正确的处理方法是什么呢?把加装消音器的方法写到另外一个接口WeaponSilencer接口中,让M4A1和USP同时实现WeaponAction和WeaponSilencer接口,而只让AK47实现WeaponAction接口,做到接口隔离,即把一个过于庞大、臃肿的接口拆分成若干个偏向细节的接口,并让相应的依赖类分别实现。

    ISP的重点内容就是:

  • 拆分臃肿的接口,让相应的依赖类只实现其真正需要实现的方法;
  • 提高内聚,减少对外交互;
  • 适度细化接口,并不是把所有方法都单独拆分出来,就是好的选择;
5.迪米特法则(Demeter Principle)

    DP原则也被称为最少知识原则或最小知道原则,其根本目的是降低类与类之间的耦合。通俗地说,就是让某个类对自己所依赖的类越少越好、知道的越少越好

    举个例子,校长在集会上进行点名,可能会点老师的名,也可能会点学生的名字;同时,老师也会对学生点名;

    符合DP原则的设计时,校长只依赖于老师,老师只依赖与学生,即:校长知道所有老师的名字,自己知道点名老师的方法;老师知道自己学生的名字,和点名自己学生的方法。那么在校长点名学生时,直接调用老师点名学生的方法,而不直接对学生进行点名,即校长不再自己记录所有学生的名字,这样在学生发生变动时,校长中的方法并不需要进行修改。在这个例子中,整个符合DP原则的耦合是:校长->老师->学生;若校长自己记录所有学生的名字,那么耦合就变成了校长->老师->学生&校长->学生,这就增加了系统的耦合度。

6.合成复用原则(Composite Reuse Principle)

    CRP原则的内容是:当要扩展类的功能时,优先考虑使用合成/聚合,而不是继承。其具体体现有很多,如桥接模式等。

    少用继承的原因在于,继承将导致系统的复杂度变大,最终使得整个系统变得难以维护。

    具体区分为:

  • 当类与类之间的关系是"Is-A"时,用继承;
  • 当类与类之间的关系是"Has-A"时,用组合(及把类A的方法拆分到若干Interfece中去,同时实现(组合)这几个接口的功能);

    参见:http://www.cnblogs.com/MRRAOBX/articles/4118938.html

               http://www.cnblogs.com/menglin2010/archive/2012/04/09/2435785.html

               http://www.cnblogs.com/linkarl/p/4854529.html

7.开闭原则(Open Close Principle)

    OCP原则就是:对扩展开放,对修改封闭。即系统进行扩展是被鼓励的,对现有系统代码进行修改是不被支持的。

    可以说,所有的设计原则的基础都是OCP原则。当你回顾刚刚提到的所有原则,就会发现处处都有OCP原则的影子。



----------------------------------------------

引用参考:http://www.uml.org.cn/sjms/201211023.asp


>>[设计模式]OOP设计模式·目录



你可能感兴趣的:(设计,架构,开发模式)