设计原则之【里氏替换原则】


全网最全最细的【设计模式】总目录,收藏起来慢慢啃,看完不懂砍我

什么是里氏替换原则

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

里氏替换原则与多态的区别

实现多态的条件:
1.继承:必须要有子类继承父类的继承关系。
2.重写:子类需要对父类中的一些方法进行重写,然后调用方法时就会调用子类重写的方法而不是原本父类的方法。
3.向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。

多态的核心概念是向上转型,这是实现多态的核心。

而里氏替换原则讲的是,子类不能改变父类原来的逻辑,子类完美继承父类的设计初衷,并做了增强。

这两者的出发点是不同的。

实例

我们举个例子,LoginLog类继承了原来的Login类,并判断是否需要打印登录日志。


public class Login{

  public Response doLogin(Request request) {
    // ...登录逻辑
  }
}

public class LoginLog extends Login {
  private Boolean openDebug;

  public LoginLog(Bookean openDebug) {
    this.openDebug= openDebug;
  }

  @Override
  public Response doLogin(Request request) {
    if (openDebug == true) {
      logger.log("xxxx进行了登录");
    }
    return super.sendRequest(request);
  }
}

public class Demo {    
  public void demoFunction(Login login) {    
    Reuqest request = new Request();
    //...省略设置request中数据值的代码...
    Response response = login.doLogin(request);
    //...省略其他逻辑...
  }
}

// 里式替换原则
Demo demo = new Demo();
demo.demofunction(new LoginLog(true););

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

乍一看上去,这不就是多态嘛,其实,里氏替换原则跟多态的核心思想是不同的,我们继续往下看,假如说LoginLog的代码稍作修改,改成下面这个样子:

public class LoginLog extends Login {
  private Boolean openDebug;

  public LoginLog(Bookean openDebug) {
    this.openDebug= openDebug;
  }

  @Override
  public Response doLogin(Request request) {
    if (openDebug == false) {
      throw new RuntimeException(...);
    }
    logger.log("xxxx进行了登录");
    return super.sendRequest(request);
  }
}

LoginLog继承了Login之后,将doLogin核心代码做了一些改变,使其部分入参的响应结果与父类不同。虽然LoginLog并不会带来编译上的错误,但是在设计上,我们认为它不符合里氏替换原则的。

多态和里式替换有点类似,但它们关注的角度是不一样的。多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法。它是一种代码实现的思路。而里式替换是一种设计原则,是用来指导继承关系中子类该如何设计的,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑以及不破坏原有程序的正确性。

哪些代码违背里氏替换原则

子类修改了父类原有的功能逻辑

比如说,订单排序,父类是按照时间正序排列,子类按照时间倒叙排列。
比如说,父类查询出用户的机构,子类却给去掉了。
比如说,父类买卖商品时不允许超额售卖,子类却允许了超额售卖

子类违背父类的输入输出、异常的细节约定

比如说,父类对于特定条件下输出为null,子类修改了这个逻辑,输出不为null了。
比如说,父类对于输入一个正整数,抛出一个异常,子类将这个异常捕获了。

总而言之

只要是父类的一切输入、输出、异常、逻辑,子类有任何的修改,就是违背里氏替换原则。

子类不能改变父类原来的逻辑,子类完美继承父类的设计初衷,并做了增强。

里氏替换原则的意义

一、改进已有实现。例如程序最开始实现时采用了低效的排序算法,改进时使用LSP(里氏替换原则)实现更高效的排序算法。
二、指导程序开发。告诉我们如何组织类和子类(subtype),子类的方法(非私有方法)要符合contract。
三、改进抽象设计。如果一个子类中的实现违反了LSP(里氏替换原则),那么是不是考虑抽象或者设计出了问题。

里氏替换最终一句话还是对扩展开放,对修改关闭,不能改变父类的入参,返回,但是子类可以自己扩展方法中的逻辑。父类方法名很明显限定了逻辑内容,比如按金额排序这种,子类就不要去重写金额排序,改成日期排序之类的,而应该抽出一个排序方法,然后再写一个获取排序的方法,父类获取排序调用金额排序,子类就重写调用排序方法,获取日期排序。

也是为了避免“二意性”,这里是只父类的逻辑和子类逻辑差别太多,读代码的人会感觉模棱两可,父类一套,子类一套,到底应该读哪种。感觉会混乱。

总之就是,子类的重写最好是扩展父类,而不要修改父类。

参考资料

王争老师《设计模式之美》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秃了也弱了。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值