【设计模式七大原则】里式替换原则
里氏替换原则(Liskov Substitution Principle)
定义
所有引用基类的地方必须能透明的使用其子类的对象。
图例
分析
父类定义了函数的“约定”(或者叫协议),那子类可以改变函数的内部实现逻辑,但不能改变函数原有的“约定”。这里的约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。
我们怎么判断有没有违背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('吃鱼')
优点
- 提高代码的重用性
- 提高代码的可扩展性
缺点
- 继承是侵入性的。只要有继承,就必须拥有父类的所有属性和方法
- 如上点,增强了耦合性。当父类被修改时,需要考虑子类的修改
注意事项
- 子类的所有方法必须在父类中声明,或者子类必须实现父类中声名的所有方法。
- 尽量把父类设计成抽象类或接口,让子类继承父类或实现父接口。增加一个新功能时,通过增加一个新的子类来实现。
适用场景
- 在类中调用其他类时,务必要使用父类或接口,否则说明类的设计已经违背了里氏替换原则。
- 如果子类不能完整地实现父类方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合等关系来代替继承。
- 点赞
- 收藏
- 关注作者
评论(0)