哪些代码是违反里氏替换原则的?
如何将代码改造成满足里氏替换原则?
里氏替换原则和多态的区别?
相比其他的设计原则,里氏替换原则还是比较简单,比较容易理解和掌握的。
里氏替换原则,英文名Liskov Substitution Principle,缩写LSP。子类对象能够替换程序中父类对象出现的任何地方,并且保证原有程序的逻辑行为不变,及正确性不发生改变。
看到这里是不是觉得多态和里氏替换原则有点相似呢?先进一步解读里氏替换原则,然后再区分二者得区别。实际上,里氏替换原则还有一个更加落地,更有指导意义得描述,那就是“Design By Contract”,中文翻译是“按照协议来设计”。也就是说,子类再设计的时候,要遵循父类的行为约定(或者协议),那子类可以改变函数的内部逻辑,但不能改变函数原有的行为约定,这里的行为约定包括:函数声明要实现的功能,对输入、输出、异常的约定,甚至包括注释中所罗列的任何特殊说明。定义中父类和子类的关系,也可以替换成接口和实现类之间的关系。
为了更好理解里氏替换原则,举几个违反里氏替换原则的例子进行说明。
1、子类违反父类声明要实现的功能
父类中提供了sortOrderByAmount()订单排序函数,是按照订单金额来排序的,而子类重写了这个sortOrderByAmount()方法之后,是按照订单的创建日期来排序。那子类的设计违反了里氏替换原则。
2、子类违背父类对输入、输出、异常的约定
在父类中,某个函数约定:运行出错时返回null,获取不到数据的时候返回空集合(empty collection);而子类在重载函数之后,实现变了,运行出错,返回异常(exception),获取不到数据返回null,那么子类的设计违背了里氏替换原则。
在父类中,某个函数约定,输入的数据可以时任意整数;而子类重载之后,只允许输入正整数,负数就抛出,也就是说子类对输入数据的校验比父类更加严格了。那么子类的设计违背了里氏替换原则
在父类中,某个函数如果只会抛出ArgumentNullException,那么子类中只允许抛出ArgumentNullException异常,任何其他异常的抛出,都会导致违反里氏替换原则。
3、子类违背父类中所罗列的任何特殊书名
父类中定义了withdraw()提现函数的注释是这么写的:“用户的体现金额不得超过账户的余额……”,当子类重写withdraw()方法后,针对VIP账号实现了透支体现的功能,也就是提现金额大于账户余额,那子类的设计不符合里氏替换原则
上面三种情况时典型的违背了里氏替换原则,还有一个小窍门可判定子类设计是否违背里氏替换原则,那就是使用 父类的单元测试去验证子类的代码。某些单元测试运行失败,那就说明子类设计违反了里氏替换原则。
多态和里氏替换原则的区别?
多态和里氏替换原则所关注的角度不同,多态是面向编程的一大特性,也是面向编程语言的一种语法,它是一种代码的实现思路。
里氏替换原则是一种设计原则,是用来指导继承关系中子类该如何设计,才能在子类替换父类的时候,不改变原有程序的逻辑以及不破坏原有程序的正确性。
参考:设计模式之美--王争