📅 2025-07-21 09:08
👤 admin
里氏替换原则(Liskov Substitution Principle, LSP)是面向对象设计的核心原则之一,由芭芭拉・利斯科夫(Barbara Liskov)在 1987 年提出。其核心思想是:子类对象必须能够替换掉它们的父类对象,而不影响程序的正确性。这意味着,子类应当遵循父类的契约,保持行为的一致性。
- 继承的正确性:确保子类不会破坏父类的原有功能。
- 多态的可靠性:通过父类引用调用子类方法时,结果应符合预期。
- 降低系统风险:避免因子类行为异常导致的潜在错误。
- 前置条件不能强化:子类方法的前置条件(输入参数)不能比父类更严格。
- 后置条件不能弱化:子类方法的后置条件(返回值)必须满足父类的要求。
- 不变式要保持:子类不能改变父类的不变量(如类的约束条件)。
- 异常要兼容:子类抛出的异常应当与父类一致或为其子类。
假设我们有一个矩形类和一个正方形类,正方形继承自矩形:
// 基类:矩形
public class Rectangle
{
public virtual double Width { get; set; }
public virtual double Height { get; set; }
public double Area() => Width * Height;
}
// 子类:正方形(错误设计)
public class Square : Rectangle
{
// 正方形的宽和高必须相等
public override double Width
{
get => base.Width;
set
{
base.Width = value;
base.Height = value;
}
}
public override double Height
{
get => base.Height;
set
{
base.Height = value;
base.Width = value;
}
}
}
这个设计违反了 LSP,因为:
// 抽象接口:四边形
public interface IQuadrilateral
{
double Width { get; }
double Height { get; }
double Area();
}
// 矩形实现
public class Rectangle : IQuadrilateral
{
public double Width { get; set; }
public double Height { get; set; }
public double Area() => Width * Height;
}
// 正方形实现
public class Square : IQuadrilateral
{
private double _side;
public double Width
{
get => _side;
set => _side = value;
}
public double Height
{
get => _side;
set => _side = value;
}
public double Area() => _side * _side;
}
// 正方形包含一个矩形实例
public class Square
{
private readonly Rectangle _rectangle = new Rectangle();
public double Side
{
get => _rectangle.Width;
set
{
_rectangle.Width = value;
_rectangle.Height = value;
}
}
public double Area() => _rectangle.Area();
}
// 基类:银行账户
public class BankAccount
{
protected double _balance;
public virtual void Withdraw(double amount)
{
if (amount > _balance)
throw new InvalidOperationException("余额不足");
_balance -= amount;
}
}
// 子类:透支账户(错误设计)
public class OverdraftAccount : BankAccount
{
private double _overdraftLimit = 1000;
// 重写取款方法,允许透支
public override void Withdraw(double amount)
{
if (amount > _balance + _overdraftLimit)
throw new InvalidOperationException("超过透支限额");
_balance -= amount;
}
}
- 前置条件被弱化:子类允许更多情况下的取款(透支),而父类不允许。
- 行为不一致:依赖父类行为的代码(如检查余额)在使用子类时会失效。
- 集合类中的协变与逆变:确保泛型集合的类型安全。
- 事件处理:子类事件的参数应与父类兼容。
- 数据库操作:子类的查询条件不能比父类更严格。
- LSP 是继承的契约:子类必须遵守父类的行为约定。
- 避免破坏父类行为:重写方法时不要改变原有的语义。
- 优先使用组合:当继承导致 LSP 违反时,考虑使用组合或接口替代。
通过遵循 LSP,代码可以保持良好的可替换性和扩展性,降低系统的耦合度。