1. 原则定义
里氏替换原则(Liskov Substitution Principle, LSP) 是SOLID五大原则中的"L",由Barbara Liskov提出:
“子类对象必须能够替换其父类对象,而不会破坏程序的正确性。”
2. 核心思想
- 子类应该扩展父类的功能,而不是改变父类的原有行为
- 客户端代码应该无需知道它使用的是父类还是子类
- 子类可以增加新方法,但不应重写父类非抽象方法并改变其行为
3. Python实现示例
3.1 违反LSP的反例
class Rectangle:def __init__(self, width, height):self.width = widthself.height = heightdef area(self):return self.width * self.heightclass Square(Rectangle):def __init__(self, size):super().__init__(size, size)# 违反LSP:改变了父类属性的行为@propertydef width(self):return self._width@width.setterdef width(self, value):self._width = self._height = value@propertydef height(self):return self._height@height.setterdef height(self, value):self._width = self._height = value# 客户端代码
def print_area(shape: Rectangle):shape.width = 5shape.height = 4print(f"Expected area: 20, Actual area: {shape.area()}")rect = Rectangle(5, 4)
print_area(rect) # 输出: Expected area: 20, Actual area: 20square = Square(5)
print_area(square) # 输出: Expected area: 20, Actual area: 16 (违反LSP)
3.2 遵循LSP的改进方案
from abc import ABC, abstractmethodclass Shape(ABC):@abstractmethoddef area(self):passclass Rectangle(Shape):def __init__(self, width, height):self.width = widthself.height = heightdef area(self):return self.width * self.heightclass Square(Shape):def __init__(self, size):self.size = sizedef area(self):return self.size ** 2def print_area(shape: Shape):print(f"Area: {shape.area()}")# 客户端代码
rect = Rectangle(5, 4)
print_area(rect) # 输出: Area: 20square = Square(4)
print_area(square) # 输出: Area: 16
4. 使用场景
- 继承体系设计:当需要扩展父类功能时
- 接口实现:保证不同实现类可以互换使用
- 多态应用:方法参数接收父类类型时,可以传入任意子类
- 单元测试:可以用Mock对象替换真实对象
5. 最佳实践
- 优先使用组合而非继承:如果难以满足LSP,考虑使用组合
- 设计可替换的接口:子类不应强化前置条件或弱化后置条件
- 避免重写非抽象方法:子类应该只扩展行为,不修改行为
- 使用类型提示:Python 3.5+的类型提示可以帮助检查LSP
- 契约式设计:明确父类的契约,子类必须遵守
6. 检测LSP违反的方法
- 子类修改了父类方法抛出的异常类型
- 子类加强了前置条件(对输入要求更严格)
- 子类弱化了后置条件(对输出保证更少)
- 子类修改了父类方法的返回值类型
- 子类改变了父类方法的副作用
7. 总结
里氏替换原则是保证面向对象设计中多态能正确工作的基础原则。在Python这样的动态类型语言中,虽然编译器不会强制检查LSP,但遵守该原则能使代码更健壮、更易维护。通过合理设计继承层次、使用抽象基类(ABC)和类型提示,可以有效实现LSP。