学习设计模式,以《设计模式之禅》为蓝本进行总结与学习,今天先记录设计模式六大原则的两个原则:单一职责原则(SRP)和里氏替换原则(LSP)。
单一职责原则@H_502_13@
Single Responsibility Principle(SRP):There should never be more than one reason for a class to change.@H_502_13@
看如下一个设计:
思考问题:
1、协议接通的变化会引起这个接口或实现类的变化吗?
2、那数据传送(想想看,电话不仅仅可以通话,还可以上网)的变化会引起这个接口或实现类的变化吗?
很显然会引起变化,这就不符合单一职责原则了,需要做如下改进:
改进后,上述两种情况的变化仅会引起一个接口的变化而不影响其他接口。
单一职责的好处:
1、类的复杂性降低,实现什么职责都有清晰明确的定义;@H_502_13@
2、可读性提高,因为复杂性降低,所以可读性提高;@H_502_13@
3、可维护性提高,可读性提高,当然更加容易维护;@H_502_13@
4、变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。@H_502_13@
单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或类设计得是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异。
下面给出一个方法关联了太多的职责:
改进的类:
改进后,调用更加明确。
对于单一职责原则,我的建议是接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。
里氏替换原则@H_502_13@
Liskov Substitution Principle(LSP),definition:@H_502_13@
1、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 substituted for o2 then S is a sub type of T.@H_502_13@
如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。@H_502_13@
2、Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.@H_502_13@
所有引用基类的地方必须能透明地使用其子类的对象。@H_502_13@
第二个定义是最清晰明确的,通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。
LSP为继承定义了良好的原则:
CS游戏中的应用类图:
如果有一把玩具枪(ToyGun),该如何实现?注意:玩具枪不可以射击。
2、子类可以有自己的个性。@H_502_13@
3、覆盖或实现父类的方法时输入参数可以被放大。@H_502_13@
里氏替换原则要求制定一个契约,就是父类或接口,这种设计方法也叫做DesignbyContract(契约设计)。契约制定了,也就同时制定了前置条件和后置条件,前置条件就是你要让我执行,就必须满足我的条件;后置条件就是我执行完了需要反馈。
方法中的输入参数称为前置条件,重载时,要求方法的输入参数类型或数量不相同,在里氏替换原则要求下,就是子类的输入参数宽于或等于父类的输入参数,也就是说你写的这个方法是不会被调用的。
4、覆写或实现父类的方法时输出结果可以被缩小。@H_502_13@
父类的一个方法的返回值是一个类型T,子类的相同方法(重载或覆写)的返回值为S,那么里氏替换原则就要求S必须小于等于T。
采用里氏替换原则的目的就是增强程序的健壮性,版本升级时也可以保持非常好的兼容性。即使增加子类,原有的子类还可以继续运行。在实际项目中,每个子类对应不同的业务含义,使用父类作为参数,传递不同的子类完成不同的业务逻辑,非常完美!