SOLID原则精解之里氏替换原则LSP---选自《敏捷软件开发:原则、模式与实践》
什么是里氏替换原则?
里氏替换原则(Liskov Substitution Principle LSP)Liskov女士在1987 年的“面向对象技术的高峰会议”上发表的一篇文章《数据抽象和层次》(Data Abstraction and Hierarchy)里提出,她指出:继承必须确保超类所拥有的性质在子类中仍然成立。
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的要求。
----------------------------
作者:“人民副首席码仔”
- 点赞
- 收藏
- 关注作者
评论(0)