翻了三本书《设计模式之禅》、《设计模式:可复用的面向对象软件元素》、《Head First 设计模式》,也看了不少博客和关于设计模式原则的文章。关于设计模式有几大原则,似乎没有严格的定论,有的说6大设计原则,有的说7大设计原则,《Head First》中更是提到了9个设计原则。 不管是多少个设计原则,最终都是希望程序达到 **“高内聚,低耦合” **,代码高度复用,具有可维护性的目的。所以多少个设计原则已经不重要了,重要的是达到怎样的目标!
我觉得7大设计原则都有必要了解和尽量向其靠拢,但是程序设计肯定是不可能完全遵守这些设计原则,但是我们的设计可以让程序更好扩展和更容易维护。
开闭原则的原文定义是:Software entities should be open for extension,but closed for modification.(软件实体应该对扩展开放,对修改关闭。)
其意思是说一个软件程序应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。但是并不意味着不对代码做任何的修改,底层模块的变更,必然要有高层模块进行耦合,我们只能尽量预测变化,但是并不能保证所有的变更都不需要修改代码。
所以说,开闭原则具有理想主义的色彩,它是面向对象设计的终极目标。
开闭原则是面向对象设计中最基础的设计原则,它也被称为设计总则,它指导我们如何建立稳定灵活的系统。
单一职责原则的原文定义是:There should never be more than one reason for a class to change.(应该有且仅有一个原因引起类的变更。)
在《设计模式之禅》中举了一个电话接口的例子。电话通话的时候有4个过程发生:拨号、通话、回应、挂机。那么写一个接口:
图-1
但是,这个IPhone 接口,并不是只有一个职责,它包含了两个职责:一个是协议管理,一个是数据传送。dial()和hangup()实现的是协议管理,分别负责拨号和挂机;chat()实现的是数据的传送。如果协议接通的变化肯定会引起接口或者实现类的变化;而数据传送的变化(电话不仅仅为了通话传送数据,还可以为上网传送数据)肯定也会引起接口或者实现类的变化,所以这里就有两个原因会引起接口或者类的变化。分析后,我们应该考虑拆分连个接口:
图-2
注意 单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或者类设计的是否优良,但是“职责”或“变化原因”都是不可度量的,因项目而异,因环境而异。 因为“职责”没有一个量化的标准,一个类到底要负责哪些职责?这些职责该怎么细化?而项目后期职责发生扩展,可能一个职责要衍生出两个职责出来,该怎么拆分?在项目时间紧迫,接口或者类非常简单,考虑人工和事件成本时,是否还要坚持 单一职责原则?这些都要根据实际情况来考量。
为什么这个原则的名字这么奇怪呢? 因为这个原则是由麻省理工学院的一位叫Barbara Liskov的女士提出来的,所以就叫里氏替换原则了。国外用发现或者创造定理、原则等是很正常的事,比如牛顿定理、法拉第、欧拉、XXX彗星等等。 里氏替换原则 有两种定义: 第一种定义:If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 is substitued for o2 then S is a subtype of T.(如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型S 是类型T的子类型。)
第二种定义:Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.(所有引用基类的地方必须能透明地使用其子类的对象。)
用通俗的话讲,就是 所有父类能出现的地方子类就可以出现,并且替换为子类也不会产生任何的错误或异常,使用者可能根本就不需要知道是父类还是子类。要做到这样,那么子类就只能扩展父类的功能,但不能修改父类原有的功能。
这一原则主要是为了规范面向对象语言的** 继承 ** 这一特性。
里氏替换原则为良好的继承定义了一个规范,其包含了4层含义:
提醒
依赖倒置原则的原始定义是: High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions. 包含了三层含义:
高层模块和低层模块很容易理解,每一个逻辑的实现都是由原子逻辑组成的,不可分割的原子逻辑就是低层模块,原子逻辑的再组装就是高层模块。那什么是抽象?什么又是细节呢?在Java 语言中,抽象就是指接口或者抽象类,两者都是不能直接被实例化的;细节就是实现类,实现接口或继承抽象类而产生的类就是细节,其特点就是可以直接被实例化。在OC 中,抽象就是协议啦,细节就是实现协议的类。 依赖倒置原则在Java 语言中的表现就是:
具体到写代码时,那就是在使用到具体类时,不直接使用具体类,而使用具体类的抽象类或接口代替。
接口隔离原则有两种定义:
每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。 举个例子就是如果接口A 中有10个接口,而实现类B 使用到了接口A 中的 5个,实现类C 使用到了接口A 中的另外 5个,那么我们应该将接口A 拆分成接口A1和接口A2。然后让实现类B 实现接口A1中的接口,实现类C 实现接口A2中的接口。 错误的设计如下图所示:
修改前(错误的设计)
经过修改后的关系如下:
修改后(更好的设计)
将一个臃肿的接口拆分为两个独立的接口,所依赖的原则就是接口隔离原则,使用多个隔离的接口,比使用一个臃肿的接口要好的多。很多IM SDK都遵守了这种原则来使某些实现来具有单聊、群聊、音视频通话 等功能。可以参考容量、云之讯、网易云信等SDK。
接口隔离原则是对接口进行规范的约束,其包含了4层含义:
迪米特法则也称为最小知识原则(Principle of Least Knowledge,PLK)。 一个对象应该对其他对象有最少的了解。简单说来,就是一个类应该对自己需要耦合或调用的类知道得最少,被耦合或调用的类的内部是如何复杂与我都没关系,我只关系呗耦合或被调用的类提供给我的方法。 迪米特法则还有一个英文解释:Only talk to your immediate friends。 每个对象都会与其他对象有耦合关系,两个对象之间的耦合就成为了朋友关系。这种关系的类型有很多,例如组合、聚合、依赖等。 简单的说,只要两个类之间有交互或关联,那么它们就是朋友关系。
合成/聚合复用原则经常又叫做合成复用原则,它的设计原则是:要尽量使用合成/聚合,尽量不要使用继承。也就是说,我们要优先考虑使用合成、聚合来实现功能,在使用合成、聚合无法实现的情况下,才考虑使用继承来实现。
其实这里最重要的地方就是区分“has-a”和“is-a”的区别。 相对于合成和聚合,继承的缺点在于:父类的方法全部暴露给子类。父类如果发生变化,子类也得发生变化;聚合的复用的时候就对另外的类依赖的比较的少。
在《Head First 设计模式》一书中整理的设计原则有:
可以看出这里的设计原则其实也是用更通俗简单的话描述了上面的7大原则,或者扩展等。正所谓一千个读者眼中就有一千个哈姆雷特,我们不应该拘泥于多少个设计原则或者设计模式,应该将重点放在如何设计出高内聚,低耦合,代码能够高度复用,具有高度可维护性,健壮的程序上。毕竟这些原则或模式都是为了我们设计程序代码,实现某些功能服务的,不是吗?
参考: 百度百科-设计模式 http://www.runoob.com/design-pattern/design-pattern-intro.html http://www.uml.org.cn/sjms/201211023.asp#6 书籍: 《Head First 设计模式》 《设计模式 - 可复用的面向对象软件元素》 《设计模式之禅》