【设计模式七大原则】里式替换原则

举报
子都爱学习 发表于 2022/02/08 21:13:42 2022/02/08
【摘要】 里氏替换原则(Liskov Substitution Principle)定义所有引用基类的地方必须能透明的使用其子类的对象。图例分析里氏代换原则可以表述为:在软件中如果能够使用基类对象,那么一定能够使用其子类对象。把基类都替换成它的子类,程序将不会产生任何错误和异常,子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的...

里氏替换原则(Liskov Substitution Principle


定义

所有引用基类的地方必须能透明的使用其子类的对象。

图例

设计模式6大原则-里氏替换原则(LSP) · Issue #14 · Cosmos-Front-end/tech-architecture ·  GitHub

分析

里氏代换原则可以表述为:在软件中如果能够使用基类对象,那么一定能够使用其子类对象。把基类都替换成它的子类,程序将不会产生任何错误和异常,子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。

父类定义了函数的“约定”(或者叫协议),那子类可以改变函数的内部实现逻辑,但不能改变函数原有的“约定”。这里的约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。

我们怎么判断有没有违背LSP呢?我觉得有两个关键点可以作为判断的依据,一个是子类有没有改变父类申明需要实现的业务功能,另一个是否违反父类关于输入、输出以及异常抛出的规定。


意义

在面向对象的思想中,一个对象就是一组状态和一系列行为的组合体。状态是对象的内在特性,行为是对象的外在特性。LSP表述的就是在同一继承体系中的队形应该具有共同的行为特征。

只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对"开-闭"原则的补充。实现"开-闭"原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。当然,如果反过来,软件单位使用的是一个子类对象的话,那么它不一定能够使用基类对象。

里氏代换原则是实现开闭原则的基础,它告诉我们在设计程序的时候进可能使用基类进行对象的定义和引用,在运行时再决定基类的具体子类型。

代码实例

我们定义一个Animal类

class Animal:
    def __init__(self, name):
        self.name = name
    def run(self):
        print("{}在跑".format(self.name))
    def fly(self):
        print('{}在飞'.format(self.name))

# 子类改变父类申明需要实现的业务功能

# 猫不会飞,没有实现父类的fly
class cat(Animal):
    def run(self):
        print("{}在跑".format(self.name))

父类设计也有问题,我们把父类和子类重写

class Animal:
    def __init__(self, name, type):
        self.name = name
        self.type = type
    def act(self):
        print("{}在{}".format(self.name, self.type))

class cat(Animal):
    def act(self):
        print("{}在{}".format(self.name, self.type))
    
    def eat(self):
        print('吃鱼')

# 违反父类关于输入、输出以及异常抛出的规定

class cat(Animal):
    def act(self):
        print("{}在{}".format(self.name, self.type))
        return True
    def eat(self):
        print('吃鱼')

优点

  • 提高代码的重用性
  • 提高代码的可扩展性

缺点

  • 继承是侵入性的。只要有继承,就必须拥有父类的所有属性和方法
  • 如上点,增强了耦合性。当父类被修改时,需要考虑子类的修改

注意事项

  • 子类的所有方法必须在父类中声明,或者子类必须实现父类中声名的所有方法。
  • 尽量把父类设计成抽象类或接口,让子类继承父类或实现父接口。增加一个新功能时,通过增加一个新的子类来实现。

适用场景

  • 在类中调用其他类时,务必要使用父类或接口,否则说明类的设计已经违背了里氏替换原则。
  • 如果子类不能完整地实现父类方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合等关系来代替继承。
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。