写点什么

设计模式之美——里式替换(LSP)

作者:GalaxyCreater
  • 2022-12-24
    广东
  • 本文字数:1166 字

    阅读完需:约 4 分钟

Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it


子类在设计的时候,要遵守父类的行为约定(或者叫协议)。父类定义了函数的行为约定,那子类可以改变函数的内部实现逻辑,但不能改变函数原有的行为约定。这里的行为约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。实际上,定义中父类和子类之间的关系,也可以替换成接口和实现类之间的关系。


例子

public class Transporter {  public Response sendRequest(Request request) {    // ...use httpClient to send request  }}
public class SecurityTransporter extends Transporter { private String appId; @Override public Response sendRequest(Request request) { if (StringUtils.isNotBlank(appId)) { request.addPayload("app-id", appId); } return super.sendRequest(request); }}
public class Demo { public void demoFunction(Transporter transporter) { transporter.sendRequest(new Request()); //...省略其他逻辑... }}
// 里式替换原则Demo demo = new Demo();demo.demofunction(new SecurityTransporter(/*省略参数*/););
复制代码


和多态区别

如果将上面 SecurityTransporter::sendRequest 改为 appId 不存在时,抛出异常:

// 改造后: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);    return super.sendRequest(request);  }}
复制代码

那么 SecurityTransporter 不符合里式替换原则,但是还是具有多态性(多态是面向对象程序的语法)。


违背 LSP 场景

1.子类违背父类声明要实现的功能

父类中提供的 sortOrdersByAmount() 订单排序函数,是按照金额从小到大来给订单排序的,而子类重写这个 sortOrdersByAmount() 订单排序函数之后,是按照创建日期来给订单排序的。那子类的设计就违背里式替换原则。


2.子类违背父类对输入、输出、异常的约定

指所有 子类和父类对输入、输出、异常处理不一致的情况

  • 父类出错时返回 null,子类出错时返回异常

  • 父类支持任何类型的整数输入,子类输入负数会抛异常

  • 父类只会抛出一种异常,子类会抛出比父类更多种的异常等等


3.子类违背父类注释中所罗列的任何特殊情况


如何快速识别违背了 LSP 的子类?

那就是拿父类的单元测试去验证子类的代码。如果某些单元测试运行失败,就有可能说明,子类的设计实现没有完全地遵守父类的约定,子类有可能违背了里式替换原则。


发布于: 2022-12-24阅读数: 23
用户头像

GalaxyCreater

关注

还未添加个人签名 2019-04-21 加入

还未添加个人简介

评论

发布
暂无评论
设计模式之美——里式替换(LSP)_设计模式_GalaxyCreater_InfoQ写作社区