里式替换替换原则是SOLID的原则当中比较容易理解的一个原则,从外表上来看跟多态的特性有点类似
这个原则用中文来说就是子类对象的对象出现的任何地方并且原有逻辑不会被破坏
接下来我们拿一个简单的继承案例来说明一下,里氏替换原则
public class Transporter {
private HttpClient httpClient; public Transporter(HttpClient httpClient) { this.httpClient = httpClient; } public Response sendRequest(Request request) { // …use httpClient to send request } } public class SecurityTransporter extends Transporter { private String appId; private String appToken; public SecurityTransporter(HttpClient httpClient, String appId, String appToken) { super(httpClient); this.appId = appId; this.appToken = appToken; } @Override public Response sendRequest(Request request) { if (StringUtils.isNotBlank(appId) && StringUtils.isNotBlank(appToken)) { request.addPayload(“app-id”, appId); request.addPayload(“app-token”, appToken); } return super.sendRequest(request); } } public class Demo { public void demoFunction(Transporter transporter) { Reuqest request = new Request(); //…省略设置request中数据值的代码… Response response = transporter.sendRequest(request); //…省略其他逻辑… } } // 里式替换原则 Demo demo = new Demo(); demo.demofunction(new SecurityTransporter(/*省略参数*/);); |
在上面代码中子类SecurityTransporter继承了父类Transporter,并且子类的sendRequest的方法当中,对两个新增的属性进行了一下校验,如果有则进行添加,没有则和父类的逻辑一样,这就完全符合里氏替换原则,因为其父类的逻辑没有受到任何破坏,且子类可以从任何地方替换父类
如果我们进行一下更改,在子类SecurityTransporter的sendRequset()方法中,对新增的两个属性进行一下校验,如果没有,我们会抛一个运行时异常,NoAuthorizationRuntimeException
// 改造前:
public class SecurityTransporter extends Transporter { //…省略其他代码.. @Override public Response sendRequest(Request request) { if (StringUtils.isNotBlank(appId) && StringUtils.isNotBlank(appToken)) { request.addPayload(“app-id”, appId); request.addPayload(“app-token”, appToken); } return super.sendRequest(request); } } // 改造后: public class SecurityTransporter extends Transporter { //…省略其他代码.. @Override public Response sendRequest(Request request) { if (StringUtils.isBlank(appId) || StringUtils.isBlank(appToken)) { throw new NoAuthorizationRuntimeException(…); } request.addPayload(“app-id”, appId); request.addPayload(“app-token”, appToken); return super.sendRequest(request); } } |
在改造之后的代码如果我们创建了一个子类对象但是没有给与新增的两个属性赋值,如果直接使用子类的sendRequest()方法的话,会爆出异常,这种设计思路是不符合里氏替换原则的
也就是子类的设计要保证替换父类的同时,不会破坏原有逻辑
那么,在开发工作中,有哪些代码违背了LSP原则
1.子类违背了父类要声明实现的功能
比如,一个父类排序sort()函数,是大到小排序,而子类重写后的函数,是从小到大,那么子类的设计就违背了里式替换原则
2.子类违背父类对输入输出以及异常时的约定
如果一个父类在出错的时候返回null,数据为空返回空集合,而子类出错时候抛出异常,数据为空返回null,那么子类也违背了里式替换原则
父类中,输入的参数可以是任何整数,而子类中只能输入正整数,那么也是违背了里式替换原则
在父类中,只会抛出ArgumentNullException异常,子类也只能抛出ArgumentNullException异常,其他形式的异常抛出,都会违背里氏替换原则
3.子类违背了父类的原本设计思想
父类定义的体现函数的注释写的是,用户的提现金额不得超过账户余额,而子类重写之后,针对VIP账号可以进行了透支提现,那么也是不符合里氏替换原则的
如果想要验证里氏替换原则,有一个小窍门,就是通过父类的单元测试去测试子类,如果失败了,可以认为是违背了里氏替换原则的
里氏替换原则是为了指导,继承关系中子类如何设计的一个原则,最核心的部分就是
子类需要遵循父类的约定,这个约定指的是函数要实现的功能,对输入 输出 异常的约定,甚至包括注释中所罗列的情况