Liskov替换原则(LSP)
OCP中,继承支持了抽象和多态特性.
- LSP:子类必须能够替换掉其基类.
- 反例:使用if/else判断类型,以便选择针对特定类型的正确行为.
- 有效性并非本质属性
- 模型的有效性,只能通过它的客户程序来表现.
- 在考虑一个特定设计是否合理时,必须要根据该设计的使用者所作出的合理假设来审视它.
- 这些合理的假设常常就是单元测试中的assert.
- 不要试图做所有的假设,而只是优先预测那些明显的对于LSP原则的违反情况,而在相关的脆弱性臭味出现时,才做其他的预测.
- IS-A是关于行为方式的.
- 对象的行为方式才是软件所关注的.
- 行为方式是可以进行合理假设的,是客户程序所依赖的.
- DBC:基于契约(Contract)设计.
- Class的编写者显示地规定针对该类的契约,Client代码的编写者通过契约获取可以依赖的行为方式.
- 契约通过为方法声明precondition和postcondition来指定.
- 弱:如果X没有遵守Y的所有约束,那么X就比Y弱.而无视X所遵守的新约束的数目.
- 断言重新声明规则:在派生类的重载中,只能使用相等或更弱的precondition来替代原始的,同时只能使用相等或者更前的postcondition来替换原始的.
- 在单元测试中指定契约.
- 提取公共部分的方法替代继承
-
- 对应于情况1,接受一个多态行为中的微妙错误和试着修改设计来完全符合LSP,两者谁更加有利是一个需要权衡(接受缺陷放弃完美)的问题.
- 总是保证子类可以替代它的基类是一个有效的管理复杂性的方法,一旦放弃了LSP,就必须要单独考虑每个子类.
- 提取公共部分,最后在代码不是很多的时候应用.如果类Line已经有很多的客户,那么提取出LinearObject是不容易的.
- 如果一组类都支持一个公共的职责,那么它们应该从一个公共的超类继承该职责.
-
- 启发式规则和习惯用法
- 在派生类中存在退化函数,预示着可能违反了LSP原则.
- 去除功能的派生类,完成的功能少于其基类的派生类是不能替换其基类的.
- 在派生类中添加了基类不会抛出的异常.基类的使用者是不会期望这些异常的.
结论. LSP原则中的子类型的可替换性(隐式可以依赖的)能够使得基类型的模块在无需修改时可以进行扩展.
子类型:可替换性的.
使用显示或者隐式的契约来定义可替换性.