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

设计原则是指导我们代码设计的一些经验总结,也就是“心法”;面向对象就是我们的“武器”;设计模式就是“招式”。

以心法为基础,以武器运用招式应对复杂的编程问题。

来吧,通过生活中一个小场景,一起系统学习这6大设计原则。

SOLID原则–SRP单一职责原则

SOLID原则–OCP开放封闭原则

SOLID法则–LSP里式替换原则

SOLID原则–ISP接口隔离原则

SOLID原则–DIP依赖反转原则

LOD迪米特法则

实习生表妹上班又闯祸了

表妹:今天上班又闯祸了😔

我:发生什么事情啦?

表妹:我不小心改了后端接口名的大小写,前端页面报错了


你看,这不就类似我们软件开发中的里式替换原则嘛。

子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。

如何理解“里式替换原则”?

实际上,里式替换原则还有一个更加能落地、更有指导意义的描述,那就是“按照协议来设计”。

子类在设计的时候,要遵守父类的行为约定(或者叫协议)。父类定义了函数的行为约定,那子类可以改变函数内部实现逻辑(重写),但不能改变函数原有的行为约定。

这里的行为约定包括:函数声明要实现的功能对输入、输出、异常的约定;甚至包含注释中所罗列的任何特殊说明

前后端协商好的接口文档,就相当于“协议”。前端和后端都分别按照这个协议独立开发,具体的实现逻辑,是递归、动态规划还是贪心,由开发者决定。

实际上,定义中父类和子类之间的关系,也可以替换成接口和实现类之间的关系。

比如,父类Transporter使用org.apache.http库中的HttpClient类传输网络数据。子类SecurityTransporter继承父类Transporter,增加了额外的功能,支持传输appID和appToken安全认证信息。

在这里插入图片描述

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

你可能会问,上面的代码设计,不就是简单利用了面向对象的多态特性吗?

“里式替换原则”就是多态吗?

里式替换原则,是实现开闭原则的重要方式之一,由于使用父类对象的地方可以使用子类对象,因此,在程序中尽量使用父类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。

那么,“里式替换原则”就是多态吗?

还是刚才那个例子,不过需要对SecurityTransporter类中sendRequest()函数稍加改造一下。改造前,我们不校验appID或者appToken是否设置;改造后,如果appID和appToken没有设置,则直接抛出NoAuthorizationRuntimeException未授权异常。改造前后的代码对比如下:

在这里插入图片描述

你看,使用改造后的代码后,如果传进demoFunction()函数的是父类Transporter对象,那demoFunction()函数并不会有异常抛出,但如果传递给demoFunction()函数的是子类SecurityTransporter对象,那demoFunction()就有可能有异常抛出。

尽管代码中抛出的是运行时异常,我们可以不在代码中显式地捕获处理,但子类替换父类传递进demoFunction函数之后,整个程序的逻辑行为就发生了改变。

虽然从定义描述和代码实现上看,多态和里式替换有点类似,但是它们关注的角度是不一样的。

多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法,是一种代码实现的思路。

而里式替换是一种设计原则,是用来指导继承关系中,子类该如何设计,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑以及不破坏原有程序的正确性。

哪些代码明显违背了“里式替换原则”?

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

父类中提供的sortOrderByAmount()订单排序函数,是按照金额从小到大来给订单排序的,而子类重写这个sortOrderByAmount()订单排序函数之后,是按照创建日期来给订单排序的。

那么,这个子类的设计就违背了里式替换原则。

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

在父类中,某个函数约定:运行出错的时候返回null;获取数据为空的时候返回空集合。而子类重载函数之后,实现变了,运行出错返回异常,获取不到数据返回null。

那么,这个子类的设计就违背了里式替换原则。

  • 子类违背父类注释中所罗列的任何特殊说明

父类中定义的withdraw()提现函数的注释是这么写的:“用户的提现金额不得超过账户余额…”,而子类重写withdraw()函数之后,针对VIP账号实现了透支提现的功能,也就是提现金额可以大于账户余额。

那么,这个子类的设计就违背了里式替换原则。

实际上,你发现没有,里式替换原则是非常宽松的。判断子类的设计实现是否违背了里式替换原则,可以拿父类的单元测试去验证子类的代码。

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

总结

里式替换原则就是子类完美继承父类的设计初衷,并做了增强(增加自己特有的方法)。

大白话就是,可以青出于蓝胜于蓝,但是祖传的东西不能变。

好啦,每个设计原则是否应用得当,应该根据具体的业务场景,具体分析。

参考

极客时间专栏《设计模式之美》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值