设计模式六大原则之里氏替换原则

来源:互联网 发布:python用什么编译器 编辑:程序博客网 时间:2024/05/22 14:32

本文参照《设计模式之禅》一书,今天给各位简单介绍一下里氏替换原则。在此之前先简单说两个名称(个人建议在学习此规则前,请各位务必理解继承的意义父子类间的关系及重载重写语法现象)

重载与重写是完全不同的语法现象,区别如下所示:

重载: 是指在一个类中定义多个方法名相同但参数列表不同的方法,在编译时,根据参数的个数和类型来决定绑定哪个方法。
重写: 是指在子类中定义和父类完全相同的方法,在程序运行时,根据对象的类型(而不是引用类型)而调用不同的方法。

里氏替换原则(LiskovSubstitution Principle,LSP)两种定义:
● 第一种定义,也是最正宗的定义:If for each object o1 of type S there is an object o2 oftype T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 issubstituted 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 useobjects of derived classes without knowing it.(所有引用基类的地方必须能透明地使用其子类的对象。)
第二个定义是最清晰明确的,通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。

里氏替换原则为良好的继承定义了一个规范,一句简单的定义包含了4层含义。
1.子类必须完全实现父类的方法
我们在做系统设计时,经常会定义一个接口或抽象类,然后编码实现,调用类则直接传入接口或抽象类,其实这里已经使用了里氏替换原则。
注意 :
(1)在类中调用其他类时务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了LSP原则。
(2)如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系, 采用依 赖、聚集、组合等关系代替继承。

2.子类可以有自己的个性
子类当然可以有自己的行为和外观了,也就是方法和属性,那这里为什么要再提呢?是因为里氏替换原则可以正着用,但是不能反过来用。在子类出现的地方,父类未必就可以胜任(之前已经说过猫的例子)。

3.重写或实现父类的方法时输入参数可以被放大
里氏替换原则要求制定一个契约,就是父类或接口,这种设计方法也叫做Design by Contract(契约设计),在面向对象程序设 计中,父类和接口本身就是规则的制定者。
注意:子类中方法的前置条件(参数类型)必须与超类中被重写的方法的前置条件(参数类型)相同或者更宽松。

4. 覆写或实现父类的方法时输出结果可以被缩小

这是什么意思呢,父类的一个方法的返回值是一个类型T,子类的相同方法(重载或覆写)的返回值为S,那么

里氏替换原则就要求S必须小于等于T,也就是说,要么S和T是同一个类型,要么S是T的子类,为什么呢?

分两种情况:

如果是覆写,父类和子类的同名方法的输入参数是相同的,两个方法的范围值S小于等于T,这是覆写的要求,

这才是重中之重,子类覆写父类的方法,天经地义。

如果是重载,则要求方法的输入参数类型或数量不相同,在里氏替换原则要求下,就是子类的输入参数宽于或

等于父类的输入参数,也就是说你写的这个方法是不会被调用的,参考上面讲的前置条件。


实战指导:


在项目中,采用里氏替换原则时,尽量避免子类的“个性”,一旦子类有“个性”,这个子类和父类之间的关系就很难

调和了,把子类当做父类使用,子类的“个性”被抹杀——委屈了点;把子类单独作为一个业务来使用,则会让代码

间的耦合关系变得扑朔迷离——缺乏类替换的标准。

阅读全文
0 0
原创粉丝点击