04. 里式替换原则介绍
04.里式替换原则介绍
目录介绍
01.问题思考的分析
02.里式替换原则描述
03.如何理解里式替换原则
04.电商案例演变过程
05.鸟类飞行演变过程
06.里氏替换优缺点
07.里式替换原则总结
推荐一个好玩网站
一个最纯粹的技术分享网站,打造精品技术编程专栏!编程进阶网
设计模式 Git 项目地址:https://github.com/yangchong211/YCDesignBlog
里式替换原则(LSP)是面向对象设计的重要原则之一,确保子类可以无缝替换父类而不破坏程序功能。本文详细介绍了 LSP 的定义、背景、理解方法及应用场景,通过电商支付和鸟类飞行案例展示了如何遵循 LSP,并分析了其优缺点。
LSP 强调子类应保持父类的行为一致性,有助于提高代码的可扩展性、可维护性和可重用性,但也可能导致过度设计。最后,对比了 LSP 与多态的区别,明确了 LSP 作为设计原则的重要性。
01.问题思考的分析
什么是里氏替换的原则,如何理解这一原则?
有那些场景满足里氏替换原则?它跟多态有何区别?
在面向对象编程中,继承是一种重要的机制,它允许我们创建一个类(子类)来继承另一个类(父类)的属性和行为。子类通过继承父类,可以重用父类的代码,并且可以添加或修改一些特定的行为。
然而,当使用继承时,必须确保子类可以无缝地替换父类,而不会破坏原有的程序功能。这就是里式替换原则的背景。
02.里式替换原则描述
里式替换原则的英文翻译是:Liskov Substitution Principle,缩写为 LSP。这个原则最早是在 1986 年由 Barbara Liskov 提出,他是这么描述这条原则的:If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program。
在 1996 年,Robert Martin 在他的 SOLID 原则中,重新描述了这个原则,英文原话是这样的:Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it。
综合两者的描述,将这条原则用中文描述出来,是这样的:
子类对象(object of subtype/derived class)能够替换程序(program)中父类对象(object of base/parent class)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。
03.如何理解里式替换原则
里氏替换原则(Liskov Substitution Principle,LSP)是设计模式六大原则之一:
子类必须能够替换父类: 子类对象可以替换父类对象,程序的行为不会发生变化。
保证行为一致性: 子类在扩展父类功能的同时,不能改变父类原有的行为。
通俗地说,如果我们在程序中使用的是一个基类对象,那么在不修改程序的前提下,用它的子类对象替换这个基类对象,程序应该仍然可以正常运行。
04.一个错误案例演变
4.1 有缺陷的代码
假设我们在电商系统中设计了一个支付类 Payment,有一个子类 CreditCardPayment 用于处理信用卡支付:
此时,如果我们在系统中使用 Payment 基类对象进行支付:
由于 CreditCardPayment 类中的逻辑限制,当支付金额超过 1000 元时会抛出异常。这导致了父类 Payment 的行为在子类 CreditCardPayment 中发生了变化,违反了里氏替换原则。
4.2 遵守里氏替换原则
为了遵循里氏替换原则,我们应该确保子类在扩展父类功能时,保持父类的行为一致性。可以通过在父类中添加必要的约束来确保子类行为的一致性:
在这个设计中,CreditCardPayment 类继承了父类 Payment 的行为,并且在支付逻辑之前调用了 super.pay(amount),确保所有支付金额都符合父类的约束。
这样,无论是使用基类对象还是子类对象,程序的行为都保持一致,遵循了里氏替换原则。
05.鸟类飞行演变过程
5.1 未遵守里氏替换原则
例如:鸟一般都会飞行,如燕子的飞行速度大概是每小时 120 千米。但是新西兰的几维鸟由于翅膀退化无法飞行。
假如要设计一个实例,计算这两种鸟飞行 300 千米要花费的时间。显然,拿燕子来测试这段代码,结果正确,能计算出所需要的时间;但拿几维鸟来测试,结果会发生“除零异常”或是“无穷大”,明显不符合预期。
未遵守里氏替换原则:
这个设计存在的问题:
几维鸟类重写了鸟类的 setSpeed(double speed) 方法,这违背了里氏替换原则。
燕子和几维鸟都是鸟类,但是父类抽取的共性有问题,几维鸟的的飞行不是正常鸟类的功能,需要特殊处理,应该抽取更加共性的功能。
5.2 遵守里氏替换原则
取消几维鸟原来的继承关系,定义鸟和几维鸟的更一般的父类,如动物类,它们都有奔跑的能力。几维鸟的飞行速度虽然为 0,但奔跑速度不为 0,可以计算出其奔跑 300 千米所要花费的时间。
06.里氏替换优缺点
子类应该能够替代父类并且表现出相同的行为,而不需要修改原有的程序逻辑。这样可以确保代码的可扩展性、可维护性和可重用性。
遵循里式替换原则的好处包括:
代码的可扩展性:可以通过添加新的子类来扩展系统的功能,而不需要修改现有的代码。
代码的可维护性:当需要修改系统的行为时,只需要修改子类的代码,而不需要修改其他相关的代码。
代码的可重用性:可以通过使用父类的对象来处理子类的对象,从而提高代码的重用性。
它也有一些潜在的缺点和限制,包括:
过度设计:过度遵循 LSP 可能导致过度设计。为了确保子类能够无缝替换父类,可能需要在子类中添加许多条件和限制,这可能会增加代码的复杂性和维护成本。
难以满足所有情况:在某些情况下,很难设计出满足 LSP 的完美继承关系。特定的业务需求和复杂性可能导致无法完全满足 LSP 的要求,需要在设计中做出权衡和妥协。
可能引入不必要的复杂性:为了满足 LSP,可能需要引入额外的抽象层次和接口,这可能增加代码的复杂性和理解难度。
07.里式替换原则总结
7.1 一些总结和分析
里氏替换原则与开闭原则的关系
里氏替换原则与开闭原则密切相关。开闭原则强调对扩展开放、对修改关闭,而里氏替换原则则确保子类能够正确地替换父类,使得扩展在不修改现有代码的情况下进行。
在电商交易系统中,遵循里氏替换原则可以确保我们在扩展支付方式、引入新的支付逻辑时,不会破坏已有系统的稳定性。例如,我们可以添加新的支付方式,而不影响原有的支付逻辑。
里式替换原则是用来指导,继承关系中子类该如何设计的一个原则
理解里式替换原则,最核心的就是理解“design by contract,按照协议来设计”这几个字。
父类定义了函数的“约定”(或者叫协议),那子类可以改变函数的内部实现逻辑,但不能改变函数原有的“约定”。
这里的约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。
要弄明白里式替换原则跟多态的区别
虽然从定义描述和代码实现上来看,多态和里式替换有点类似,但它们关注的角度是不一样的。
多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法。它是一种代码实现的思路。多态是指,子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。
里式替换是一种设计原则,用来指导继承关系中子类该如何设计,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑及不破坏原有程序的正确性。
7.2 里式替换原则总结
里式替换问题思考:什么是里氏替换的原则?有那些场景满足里氏替换原则?它跟多态有何区别?
如何理解里式替换原则:子类可以替换父类,并且保证原有的逻辑不变以及正确性不被破坏。
列举一个里氏替换的场景:比如支付宝,微信支付。将通用支付校验逻辑放到父类中,支付子类继承父类进行支付,支付金额都符合父类的约束。
里式替换原则的背景:面向对象中继承是一种机制,子类可以继承父类属性和行为。当使用继承时,子类不会破坏父类原有程序功能,这就是里氏替换的背景。
实现里式替换原则的方式:应该确保子类在扩展父类功能时,保持父类的行为一致性。简单说就是父类抽取通用的逻辑。
里式替换原则的案例教学:通用支付类中,有对金额进行校验,微信和支付宝支付子类通过继承父类,分别拓展自己的业务逻辑,且金额校验逻辑受到父类的约束。
里式替换原则的优点:子类替代父类行为,且不需要修改原有逻辑。可以确保代码可拓展,可维护,可重用等优点。
里式替换原则的缺点:存在过度设计,需要引入额外的抽象层次可能增加代码复杂性,等等。
总结如何理解里式替换原则:1.有继承关系;2.子类遵循父类约定;3.子类拓展行为不破坏父类行为。
里式替换原则跟多态的区别:多态是面向对象特性,是一个语法,子类可以替换父类实现。里氏替换是设计原则,是指子类设计要保证替换父类时,不改变原有逻辑和正确性!
评论