里氏替换原则(Liskov Substitution Principle)

本文探讨了里氏替换原则(LSP)在软件设计中的重要性,解释了如何避免因子类修改父类方法而导致的故障,提出了继承与委托结合的设计方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于LSP(里氏替换原则)这里做一些简要的说明以帮助理解。

LSP的引入:
父类(基类)中已经实现好的方法已经拥有了该方法相关的行为规范和契约,如果子类在添加新功能之外任意的修改父类中已经实现好的方法就会破坏父类中的规范与契约,导致在引用父类的地方如果使用子类的实现,可能会导致原有的功能遭到破坏,与此同时,整个程序的继承树此时也已经被破坏。而LSP就是防止这种可能导致故障情况出现而提出的。

里氏替换原则的定义
1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子型。
2:所有引用基类的地方必须能透明地使用其子类的对象。

说明性的例子:
现在有类A完成了功能P1,但我们需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P2由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会修改类A中有关P1功能的实现,导致原来的程序可能出现故障。解决方案是当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。

这里举出一个违背LSP的例子

void DrawShape(const Shape& s)
{
     if (typeid(s) == typeid(Square))
              DrawSquare(static_cast<Square&>(s)); 
     else if (typeid(s) == typeid(Circle))
              DrawCircle(static_cast<Circle&>(s));
 }   

说明:
在实际中,我们大多会通过重写父类的方法来完成新的功能,简单的实现方式的代价就是整个继承体系的可复用性会比较差。如果非要重写父类的方法,比较通用的做法是子类可以扩展父类的功能,但不能改变父类原有的功能。
我们可以通过这种方式实现:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合,组合等关系代替,也就是讲继承与委托结合,继承不变,将可变提取交给其他类实现,进而实现功能的委托,这样也满足开放封闭原则(Open Closed Principle):设计良好的代码可以不通过修改而扩展,新的功能通过添加新的代码来实现,而不需要更改已有的可工作的代码。

总结:
关于如何在继承中满足LSP,我们可以这么做

  1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。

  2. 子类中可以增加自己特有的方法。

  3. 当子类覆盖或实现父类的(抽象)方法时,方法的前置条件要比父类方法更宽松。

  4. 当子类覆盖或实现父类的(抽象)方法时,方法的后置条件要比父类更严格。

而更好解决办法是将继承与委托结合,实现不改动源代码仅通过增加类或者接口实现功能的增加。(相关内容可以参看我的另一篇博客:合成复用原则(Composite Reuse Principle)。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值