最近在看《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原则,势必造成颗粒化过于严重的问题,所以当你的方法:
引用: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原则就是:
对于,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原则可以这么总结:
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的重点内容就是:
DP原则也被称为最少知识原则或最小知道原则,其根本目的是降低类与类之间的耦合。通俗地说,就是让某个类对自己所依赖的类越少越好、知道的越少越好。
举个例子,校长在集会上进行点名,可能会点老师的名,也可能会点学生的名字;同时,老师也会对学生点名;
符合DP原则的设计时,校长只依赖于老师,老师只依赖与学生,即:校长知道所有老师的名字,自己知道点名老师的方法;老师知道自己学生的名字,和点名自己学生的方法。那么在校长点名学生时,直接调用老师点名学生的方法,而不直接对学生进行点名,即校长不再自己记录所有学生的名字,这样在学生发生变动时,校长中的方法并不需要进行修改。在这个例子中,整个符合DP原则的耦合是:校长->老师->学生;若校长自己记录所有学生的名字,那么耦合就变成了校长->老师->学生&校长->学生,这就增加了系统的耦合度。
6.合成复用原则(Composite Reuse Principle)
CRP原则的内容是:当要扩展类的功能时,优先考虑使用合成/聚合,而不是继承。其具体体现有很多,如桥接模式等。
少用继承的原因在于,继承将导致系统的复杂度变大,最终使得整个系统变得难以维护。
具体区分为:
参见: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设计模式·目录