先来抛一个问题,设计模式到底有几个原则?
翻了三本书《设计模式之禅》、《设计模式:可复用的面向对象软件元素》、《Head First 设计模式》,也看了不少博客和关于设计模式原则的文章。关于设计模式有几大原则,似乎没有严格的定论,有的说6大设计原则,有的说7大设计原则,《Head First》中更是提到了9个设计原则。
不管是多少个设计原则,最终都是希望程序达到 “高内聚,低耦合” ,代码高度复用,具有可维护性的目的。所以多少个设计原则已经不重要了,重要的是达到怎样的目标!设计原则
我觉得7大设计原则都有必要了解和尽量向其靠拢,但是程序设计肯定是不可能完全遵守这些设计原则,但是我们的设计可以让程序更好扩展和更容易维护。
1.开闭原则(Open Closed Principle,OCP)
开闭原则的原文定义是:Software entities should be open for extension,but closed for modification.(软件实体应该对扩展开放,对修改关闭。)
2.单一职责原则(Single Responsiblity Principle ,SRP)
单一职责原则的原文定义是:There should never be more than one reason for a class to change.(应该有且仅有一个原因引起类的变更。)
注意
单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或者类设计的是否优良,但是“职责”或“变化原因”都是不可度量的,因项目而异,因环境而异。 因为“职责”没有一个量化的标准,一个类到底要负责哪些职责?这些职责该怎么细化?而项目后期职责发生扩展,可能一个职责要衍生出两个职责出来,该怎么拆分?在项目时间紧迫,接口或者类非常简单,考虑人工和事件成本时,是否还要坚持 单一职责原则?这些都要根据实际情况来考量。
3.里氏替换原则(Liskov Substitution Principle,LSP)
为什么这个原则的名字这么奇怪呢?
因为这个原则是由麻省理工学院的一位叫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层含义:
* 1.子类可以实现父类的抽象方法,但是不能覆写父类的非抽象方法。 * 2.子类可以添加自己特有的属性或者方法。 * 3.子类覆写或者实现父类的方法时,输入的参数应该比父类的参数条件更宽松。 * 4.子类覆写或实现父类的方法时,返回的结果应该比父类的返回结果更严格。提醒
1. 在使用父类的地方可以替换为子类,但是反过来在使用子类的地方却不一定能替换成父类。 2. 如果父类的某些方法在子类中发生了“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合灯关系代替继承。
4.依赖倒置原则(Dependence Inversion Principle)
依赖倒置原则的原始定义是:
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 语言中的表现就是: * 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的; * 接口或抽象类不依赖实现类; * 实现类依赖接口或抽象类。具体到写代码时,那就是在使用到具体类时,不直接使用具体类,而使用具体类的抽象类或接口代替。
5.接口隔离原则(Interface Segregation Principle,ISP)
接口隔离原则有两种定义:
* Clients should not be forced to depend upon interfaces that they don’t use.(客户端不应该依赖它不需要的接口。) * The dependency of one class to another one should depend on the smallest possible interface.(类间的依赖关系应该建立在最小的接口上。)每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。
举个例子就是如果接口A 中有10个接口,而实现类B 使用到了接口A 中的 5个,实现类C 使用到了接口A 中的另外 5个,那么我们应该将接口A 拆分成接口A1和接口A2。然后让实现类B 实现接口A1中的接口,实现类C 实现接口A2中的接口。 错误的设计如下图所示:经过修改后的关系如下:
将一个臃肿的接口拆分为两个独立的接口,所依赖的原则就是接口隔离原则,使用多个隔离的接口,比使用一个臃肿的接口要好的多。很多IM SDK都遵守了这种原则来使某些实现来具有单聊、群聊、音视频通话 等功能。可以参考容量、云之讯、网易云信等SDK。
接口隔离原则是对接口进行规范的约束,其包含了4层含义:
* 1.接口要尽量小,但是也有一定的限度。因为过度小的接口会使接口变多,让程序变得复杂(我们总不能把每一个接口方法都定义在一个接口类里面吧)。 * 2.接口要高内聚。高内聚可以提高接口、类、模块的处理能力,减少对外的交互。具体到实际开发,就是在接口中尽量少公布 方法。 * 3.为依赖接口的类定制服务,只暴漏给它需要的方法,它不需要的方法则隐藏起来。 * 4.接口的设计要有限度。接口的设计粒度越小,系统越灵活,这是不争的事实。但是,灵活的同时也使得接口类增加,是项目接口变得复杂。所以我们设计接口时,要把握一个“度”,接口不能太臃肿,也不能太小。6.迪米特法则(Law of Demeter, LoD)
迪米特法则也称为最小知识原则(Principle of Least Knowledge,PLK)。
一个对象应该对其他对象有最少的了解。简单说来,就是一个类应该对自己需要耦合或调用的类知道得最少,被耦合或调用的类的内部是如何复杂与我都没关系,我只关系呗耦合或被调用的类提供给我的方法。 迪米特法则还有一个英文解释:Only talk to your immediate friends。 每个对象都会与其他对象有耦合关系,两个对象之间的耦合就成为了朋友关系。这种关系的类型有很多,例如组合、聚合、依赖等。 简单的说,只要两个类之间有交互或关联,那么它们就是朋友关系。7.合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)
合成/聚合复用原则经常又叫做合成复用原则,它的设计原则是:要尽量使用合成/聚合,尽量不要使用继承。也就是说,我们要优先考虑使用合成、聚合来实现功能,在使用合成、聚合无法实现的情况下,才考虑使用继承来实现。
其实这里最重要的地方就是区分“has-a”和“is-a”的区别。
相对于合成和聚合,继承的缺点在于:父类的方法全部暴露给子类。父类如果发生变化,子类也得发生变化;聚合的复用的时候就对另外的类依赖的比较的少。其他的设计原则整理
在《Head First 设计模式》一书中整理的设计原则有:
* 封装变化 * 多用组合,少用继承 * 针对接口编程,不针对实现编程 * 为交互对象之间的松耦合设计而努力 * 类应该对扩展开放,对修改关闭。 * 依赖抽象,不要依赖具体类。 * 只和朋友谈 * 别找我,我会找你。 * 类应该只有一个改变的理由。可以看出这里的设计原则其实也是用更通俗简单的话描述了上面的7大原则,或者扩展等。正所谓一千个读者眼中就有一千个哈姆雷特,我们不应该拘泥于多少个设计原则或者设计模式,应该将重点放在如何设计出高内聚,低耦合,代码能够高度复用,具有高度可维护性,健壮的程序上。毕竟这些原则或模式都是为了我们设计程序代码,实现某些功能服务的,不是吗?
参考:
书籍: 《Head First 设计模式》 《设计模式 - 可复用的面向对象软件元素》 《设计模式之禅》