一、概念
里氏替换原则:Liskov Substitution Principle,检查LSP。子类对象能够替换程序中父类对象出现的任何地方,并能保证替换完后程序的逻辑行为不变以及替换前后的逻辑语义和结果正确性不被破坏。
例如:父类逻辑是a+b,子类逻辑是a-b,参数和返回都一样,但是逻辑变了,这就不符合里氏替换原则。逻辑语义和结果的正确性得到了破坏。
二、案例
如下HealthCheck健康检查父类,有MysqlHealthCheck子类继承它,基本语义就是连接到Mysql发SELECT 1;
。
public class HealthCheck{
public Response doCheck(HealthCheckEntity entity) {
// ...
}
}
public class MysqlHealthCheck extends HealthCheck {
private String username;
private String pwd;
public MysqlHealthCheck (HealthCheckEntity entity, String username, String pwd) {
super(entity);
this.username= username;
this.pwd= pwd;
}
@Override
public Response sendRequest(HealthCheckEntity entity) {
if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(pwd)) {
entity.setUsername(username);
entity.setPwd(pwd);
}
return super.sendRequest(entity);
}
}
public class Demo {
public void demoFunction(HealthCheck healthCheck) {
HealthCheckEntity entity = new HealthCheckEntity ();
//...省略设置request中数据值的代码...
Response response = healthCheck.sendRequest(entity);
//...省略其他逻辑...
}
}
// 里式替换原则
Demo demo = new Demo();
demo.demofunction(new MysqlHealthCheck (/*省略参数*/););
在上面的代码中,子类MysqlHealthCheck的设计完全符合里氏替换原则,可以无缝替换父类出现的任何位置并且原代码逻辑行为不变且正确性也不会得到破坏。但是发现这不就是多态吗?和多态没啥区别呀。下面来看和多态的区别。
三、和多态的区别
也称哪些设计违背了里氏替换原则
- 子类违背父类声明要实现的功能
最简单的例子就是父类是按照时间倒叙排序,子类是按照时间升序排序。
- 子类违背父类对出入参以及异常的约定
比如:父类要求运行报错的时候返回null,没查到数据但是没报错返回
new Object();
,但是子类重写过后的方法报错直接返回异常了,得不到父类期待的null值。
- 子类违背父类注释中所罗列的任何一个特殊说明
比如:父类写的是参数a不能大于100,而子类重写后的判断条件没有这个100限制,这也算违背里氏替换原则。
以上三点,多态是完全不管的。所以里氏替换是事精,而多态很随和。
违背里氏替换的案例说明:
假设上面的HealthCheck的子类MysqlHealthCheck的数据库用户名密码没传的话就抛出异常,这就违背了里氏替换,因为父类要求不做任何限制,而你却强行往里加。
// 其他类不变
public class MysqlHealthCheck extends HealthCheck {
private String username;
private String pwd;
public MysqlHealthCheck (HealthCheckEntity entity, String username, String pwd) {
super(entity);
this.username= username;
this.pwd= pwd;
}
@Override
public Response sendRequest(HealthCheckEntity entity) {
// 加了限制,违背了里氏替换原则
if (StringUtils.isBlank(username) || StringUtils.isBlank(pwd)) {
throw new RuntimeException();
}
entity.setUsername(username);
entity.setPwd(pwd);
return super.sendRequest(entity);
}
}
四、总结
子类可以新增逻辑或者优化,但是不允许改变父类的语义,比如典型的就是父类要求按照id倒叙排序,你子类不可弄成按照id升序排序。