Skip to main content
  1. Posts/

OODP2 里氏替换原则(LSP)

·251 words·2 mins·
Eric Linus
Author
Eric Linus
北京邮电大学软件工程专业本科生,主要语言C++,对系统编程,数据库和AI系统交叉感兴趣。熟悉C++/Python/C#/Java/Rust。Github:@n00bme0w
Table of Contents

父类的对象可以被子类的对象替换,而程序的行为不会发生变化
#

例子
#

我们有Rectangle类

public class Rectangle
{
    public int Height{get; set; }
    public int Width { get; set; }

    public Rectangle()
    {
        
    }

    public Rectangle(int width, int height)
    {
        Width = width;
        Height = height;
    }

    public override string ToString()
    {
        return $"{nameof(Width)}: {Height}, {nameof(Height)}: {Height}";
    }
}

基于Rectangle类再写Square类

public class Square : Rectangle
{
    public new int Width
    {
        set { base.Width = base.Height = value; }
    }

    public new int Height
    {
        set { base.Width = base.Height = value; }   
    }
}

注意到我们用new关键字hide了父类的Width和Height

此时Square和Rectangle都可以正常工作

Rectangle rc = new Rectangle();
rc.Width = 20;
rc.Height = 40;
Console.WriteLine("Area: {0}", Area(rc));
Square sq = new Square();
sq.Width = 4;
Console.WriteLine("Area {0}", Area(sq));

可以看到结果正常

而由于正方形是一种长方形,我们可以使用父类型Rectangle来持有子类型Square的引用。

那么问题发生了

Rectangle sq = new Square();
sq.Width = 4;
Console.WriteLine("Area {0}", Area(sq));
输出: Area 0

这就违反了里氏替换原则,当子类的对象替换父类时发生了错误。

修改方案
#

将Rectangle中Width和Height的get,set修改为虚,使父类引用执行子类的函数

public virtual int Height{get; set; }
public virtual int Width { get; set; }

同时将Square中的用new来hide父类型改为用override来重写父类虚函数

public override int Width
{
    set { base.Width = base.Height = value; }
}

public override int Height
{
    set { base.Width = base.Height = value; }   
}

则现在不会出现错误

特性里氏替换原则 (LSP)虚函数/重写 (virtual/override)
本质设计原则 (Principle)语法机制 (Syntax)
目的指导如何正确地建立继承关系,确保行为兼容性。实现运行时多态,允许子类定制方法实现。
关系目标 (What & Why)主要实现手段 (How)

C#, C++和Java的虚函数辨析
#

特性C#Java
声明可重写方法使用 virtual 关键字默认就是“虚”的,无需关键字
重写方法使用 override 关键字使用 @Override 注解(最佳实践)
阻止重写方法标记为 virtual使用 final 关键字
隐藏方法(非多态)使用 new 关键字无关键字(但不推荐,会产生警告)
特性C#C++
默认行为非虚拟 (方法默认不能被重写)非虚拟 (方法默认不具有多态性)
实现多态基类 virtual + 派生类 override基类 virtual (派生类推荐使用 override)
设计哲学“默认关闭”:出于安全和性能,需要显式开放重写能力。“不为未使用的东西付出代价”:需要多态时,显式请求并承担其开销。
你的做法慎重选择哪些方法设为virtual。只开放需要扩展的点。必须为所有需要多态的方法加上virtual,否则无法按预期工作。