设计模式之禅笔记--面向对象设计六大原则之二

来源:互联网 发布:人工智能阅读语文答案 编辑:程序博客网 时间:2024/06/15 01:16

第二章  里氏替换原则(LSR)

里氏替换原则( Liskov Substitution Principle, LSP) , 什么是里氏替换原则呢? 它有两种定义:

l 第一种定义, 也是最正宗的定义:If for each object o1 of type S thereis 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 asubtype of T.( 如果对每一个类型为S的对象o1, 都有类型为T的对 象o2, 使得以T定义的所有程序P在所有的对象o1都代换成o2时, 程序P的行为没有发生变化, 那么类型S是类型T的子类型。 )

l  第二种定义: Functionsthat use pointers or references to base classes must be able to use
objects of derived classes withoutknowing it.( 所有引用基类的地方必须能透明地使用其子类的对象。 )第二个定义是最清晰明确的, 通俗点讲, 只要父类能出现的地方子类就可以出现, 而且替换为子类也不会产生任何错误或异常, 使用者可能根本就不需要知道是父类还是子类。但是, 反过来就不行了, 有子类出现的地方, 父类未必就能适应。

里氏替换原则为良好的继承定义了一个规范, 一句简单的定义包含了4层含义。

1.子类必须完全实现父类的方法

  • 注意 在类中调用其他类时务必要使用父类或接口,如果不能使用父类或接口, 则说明类的设计已经违背了LSP原则。
  • 注意 如果子类不能完整地实现父类的方法, 或者父类的某些方法在子类中已经发生“畸变”, 则建议断开父子继承关系, 采用依赖、 聚集、 组合等关系代替继承。

2.子类可以有自己的个性

向下转型( downcast) 是不安全的, 从里氏替换原则来看,就是有子类出现的地方父类未必就可以出现。

3.覆盖或实现父类的方法时输入参数可以被放大

 

l 方法中的输入参数称为前置条件。里氏替换原则也要求制定一个契约,就是父类或接口, 这种设计方法也叫做Design byContract( 契约设计) , 与里氏替换原则有着异曲同工之妙。 契约制定了, 也就同时制定了前置条件和后置条件,前置条件就是你要让我执行, 就必须满足我的条件; 后置条件就是我执行完了需要反馈, 标准是什么。

n 例如:父类方法的输入参数是HashMap类型, 子类的输入参数是Map类型(Hash map<Map), 也就是说子类的输入参数类型的范围扩大了, 子类代替父类传递到调用者中, 子类的方法永远都不会被执行。 这是正确的, 如果你想让子类的方法运行, 就必须覆写父类的方法。

l 子类在没有覆写父类的方法的前提下,子类方法被执行了, 这会引起业务逻辑混乱, 因为在实际应用中父类一般都是抽象类, 子类是实现类, 你传递一个这样的实现类就会“歪曲”了父类的意图, 引起一堆意想不到的业务逻辑混乱, 所以子类中方法的前置条件必须与超类中被覆写的方法的前置条件相同或者更宽松。

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

这是什么意思呢, 父类的一个方法的返回值是一个类型T, 子类的相同方法( 重载或覆写) 的返回值为S, 那么里氏替换原则就要求S必须小于等于T, 也就是说, 要么S和T是同一个类型,要么S是T的子类, 为什么呢? 分两种情况,

  • l  如果是覆写, 父类和子类的同名方法的输入参数是相同的, 两个方法的范围值S小于等于T, 这是覆写的要求, 这才是重中之重, 子类覆写父类的方法,天经地义。
  • l  如果是重载, 则要求方法的输入参数类型或数量不相同, 在里氏替换原则要求下, 就是子类的输入参数宽于或等于父类的输入参数, 也就是说你写的这个方法是不会被调用的,参考上面讲的前置条件。

小结:

        里氏替换原则,不考虑覆写时,简单来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。



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