SOLID原则精解之里氏替换原则LSP---选自《敏捷软件开发:原则、模式与实践》

举报
技术火炬手 发表于 2020/06/24 15:02:06 2020/06/24
【摘要】 里氏替换原则(Liskov Substitution Principle LSP)Liskov女士在1987 年的“面向对象技术的高峰会议”上发表的一篇文章《数据抽象和层次》(Data Abstraction and Hierarchy)里提出,她指出:继承必须确保超类所拥有的性质在子类中仍然成立。

什么是里氏替换原则?

里氏替换原则(Liskov Substitution Principle LSP)Liskov女士在1987 年的“面向对象技术的高峰会议”上发表的一篇文章《数据抽象和层次》(Data Abstraction and Hierarchy)里提出,她指出:继承必须确保超类所拥有的性质在子类中仍然成立。

image.png

Liskov女士是美国计算机科学家,是08年图灵奖获得者 & 04年冯诺依曼奖得主,是美国工程院院士 & 美国艺术与科学院院士 & 美国计算机协会会士,也是美国第一位计算机女博士。

里氏替换原则是面向对象设计的基本原则,是继承复用的基石

通俗一点讲就是:LSP要求在软件代码中,父类对象出现的地方被替换成子类可以无差错的运行。换言之,引用基类的地方必须能够透明的使用子类对象,行为不会改变。

里氏替换原则的约束

要实现上述目标,意味着符合里氏替换原则的设计需要满足如下约束:

继承体系模塑的应该是IS-A关系,比如猫是一种动物,子类对象可以完全替代父类对象。

应把基类设计成抽象类,而非具象类;应从抽象类派生子类,而不应从具象类继承。

子类应该实现父类的抽象方法,而不应该重写父类的已实现方法。

子类可以扩充新的功能,而不应该改变父类已有的功能。

子类不能增添任何父类没有的附加约束。

里氏替换原则能带来哪些好处?

里氏替换原则符合开闭原则(对扩展开放对修改封闭)的设计要求,子类可以扩充父类功能,而不会改变父类的功能。

它克服了继承中重写父类造成的可复用性变差的缺点。

它为系统正确性提供保障,即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。

实施要点

行为集中的方向向上(向抽象基类的方向),数据集中的方向向下(向具象子类的方向)。

违背LSP的最常见情况就是父子类都是可实例化的可具象类,不应该从可具象的类派生,而应该从抽象类或者接口派生。

基于契约的设计(DBC:Design By Contract),契约是通过为每个函数声明的前置条件和后置条件来指定的,要是一个方法得以正确执行,调用者需要确保满足前置条件,实现者需要确保符合后置条件。

小结

LSP强调,我们编写代码,要确保父类出现的地方,可以用子类对象安全的替换掉父类对象,而不会引起任何运行时错误,我们的设计和实现要满足这一点,否则就是违背里氏替换原则。

这意味我们要遵守契约,可以override父类接口,但不可以overwrite父类实现,不能破坏父类的行为,遵守LSP将使得程序更健壮,它是一个设计指引,更是一种编程约束,无它。

经典示例

正方形不是长方形。

鸵鸟不是鸟(见示例)。

-Cpp 代码
01
class Bird
02
{
03
public:
04
    virtual ~Bird() {}
05
 
06
    virtual void setFlySpeed(double flySpeed)
07
    {
08
        this->flySpeed = flySpeed;
09
    }
10
 
11
    double fly(double distance)
12
    {
13
        double f = distance / flySpeed;
14
        doFly(f);
15
        return f;
16
    }
17
 
18
private:
19
    virtual void doFly(double time) = 0;
20
 
21
    double flySpeed;
22
};
23
 
24
class Swallow : public Bird  //燕子
25
{
26
    void doFly(double time) override
27
    {
28
 
29
    }
30
};
31
 
32
class Ostrich : public Bird //鸵鸟
33
{
34
    void doFly(double time) override
35
    {
36
 
37
    }
38
 
39
    void setFlySpeed(double flySpeed) override
40
    {
41
        Bird::setFlySpeed(0); //因为鸵鸟不会飞,所以override后的setFlySpeed直接设置为0
42
    }
43
};
44
 
45
//测试鸟的飞翔,符合LSP的话,用Bird的子类对象调用该接口,都应该正常运行的
46
void LSPTest4BirdFly(Bird& b, double distance)
47
{
48
    double time = b.fly(distance);
49
}
50
 
51
int main()
52
{
53
    Swallow s;
54
    LSPTest4BirdFly(s, 100); //ok
55
 
56
    Ostrich o;
57
    LSPTest4BirdFly(o, 100); //not ok
58
 
59
    return 0;
60
}

鸵鸟不会飞,所以鸵鸟的设置飞行速度接口,需要被override,被强制设置为0。

这样导致LSPTest4BirdFly接口在传入鸵鸟对象的时候,不能正常work,出现异常,违背LSP的要求。

----------------------------

作者:“人民副首席码仔”

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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