本文摘自:设计模式之美:17 | 理论三:里式替换(LSP)跟多态有何区别?哪些代码违背了LSP?

定义

里式替换原则的英文翻译是:Liskov Substitution Principle,缩写为 LSP。

子类对象(object of subtype/derived class)能够替换程序(program)中父类对象(object of base/parent class)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。

这么说还是比较抽象,我们通过一个例子来解释一下。如下代码中,父类 Transporter 使用 org.apache.http 库中的 HttpClient 类来传输网络数据。子类 SecurityTransporter 继承父类 Transporter,增加了额外的功能,支持传输 appId 和 appToken 安全认证信息。

  1. public class Transporter {
  2. private HttpClient httpClient;
  3. public Transporter(HttpClient httpClient) {
  4. this.httpClient = httpClient;
  5. }
  6. public Response sendRequest(Request request) {
  7. // ...use httpClient to send request
  8. }
  9. }
  10. public class SecurityTransporter extends Transporter {
  11. private String appId;
  12. private String appToken;
  13. public SecurityTransporter(HttpClient httpClient, String appId, String appToken) {
  14. super(httpClient);
  15. this.appId = appId;
  16. this.appToken = appToken;
  17. }
  18. @Override
  19. public Response sendRequest(Request request) {
  20. if (StringUtils.isNotBlank(appId) && StringUtils.isNotBlank(appToken)) {
  21. request.addPayload("app-id", appId);
  22. request.addPayload("app-token", appToken);
  23. }
  24. return super.sendRequest(request);
  25. }
  26. }
  27. public class Demo {
  28. public void demoFunction(Transporter transporter) {
  29. Reuqest request = new Request();
  30. //...省略设置request中数据值的代码...
  31. Response response = transporter.sendRequest(request);
  32. //...省略其他逻辑...
  33. }
  34. }
  35. // 里式替换原则
  36. Demo demo = new Demo();
  37. demo.demofunction(new SecurityTransporter(/*省略参数*/););

在上面的代码中,子类 SecurityTransporter 的设计完全符合里式替换原则,可以替换父类出现的任何位置,并且原来代码的逻辑行为不变且正确性也没有被破坏。

总结

里式替换原则是用来指导,继承关系中子类该如何设计的一个原则。理解里式替换原则,最核心的就是理解“design by contract,按照协议来设计”这几个字。父类定义了函数的「约定」(或者叫协议),那子类可以改变函数的内部实现逻辑,但不能改变函数原有的「约定」。这里的约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。

多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法。里式替换是一种设计原则,是用来指导继承关系中子类该如何设计的,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑以及不破坏原有程序的正确性。
**