1 里氏替换原则定义
Liskov于1987年提出了一个关于继承的原则“Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.”——“继承必须确保父类所拥有的性质在子类中仍然成立。”也就是说,当一个子类的实例应该能够替换任何其父类的实例时,它们之间才具有is-A关系。该原则称为Liskov Substitution Principle——里氏替换原则。
2 经典案例
2.1 正方形不是长方形
1 public class Rectangle 2 { 3 public virtual int Width { get; set; } 4 public virtual int Height{ get; set; } 5 public int Area() 6 { 7 return Width * Height; 8 } 9 } 10 public class Square : Rectangle 11 { 12 public override int Width 13 { 14 get 15 { 16 return base.Width; 17 } 18 set 19 { 20 base.Width = value; 21 base.Height = value; 22 } 23 } 24 public override int Height 25 { 26 get 27 { 28 return base.Height; 29 } 30 set 31 { 32 base.Height = value; 33 base.Width = value; 34 } 35 } 36 }
Square继承Rectangle。按照里氏替换原则原则,只要是父类出现的地方都可以用子类进行替换。
1 public static void TestMethod(Rectangle rec) 2 { 3 rec.Width = 10; 4 rec.Height = 15; 5 var area = rec.Area(); 6 Assert.AreEqual(150, area); 7 }
那么该方法中的Rectangle rec是可以替换成Square的。我们试试:
1 static void Main(string[] args) 2 { 3 Rectangle r = new Rectangle(); 4 TestMethod(r); 5 Square s = new Square(); 6 TestMethod(s); 7 }
运行结果如下:
说明正方形是特殊的长方形违背了里氏替换原则。
2.2 鸵鸟非鸟
根据生物学的定义鸵鸟实实在在是鸟类,有羽毛几乎覆盖全身的卵生脊椎动物,温血卵生,用肺呼吸,几乎全身有羽毛,后肢能行走,前肢变为翅,大多数能飞。定义一个鸟类,定义一个继承鸟类的鸵鸟类。
1 public class Bird 2 { 3 public virtual int FlySpeed{get;set;} 4 public void Fly() { } 5 } 6 public class Ostrich : Bird 7 { 8 public override int FlySpeed 9 { 10 get 11 { 12 return base.FlySpeed; 13 } 14 set 15 { 16 base.FlySpeed = 0;//不会飞 飞不起来 17 } 18 } 19 }
鸵鸟不会飞啊,所以速度为0.
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Ostrich os = new Ostrich(); 6 var time = CalculationFlyTime(os); 7 Console.ReadKey(); 8 } 9 10 public static double CalculationFlyTime(Bird bird) 11 { 12 return 100 / bird.FlySpeed; 13 } 14 15 }
运行程序报错:
3.总结
所谓对象是一组状态和一系列行为的组合。状态是对象的内在特性,行为是对象的外在特性。LSP所表述的就是在同一个继承体系中的对象应该有共同的行为特征。我们在设计对象时是按照行为进行分类的,只有行为一致的对象才能抽象出一个类来。
设置长方形的长度的时候,它的宽度保持不变,设置宽度的时候,长度保持不变。正方形的行为:设置正方形的长度的时候,宽度随之改变;设置宽度的时候,长度随之改变。所以,如果我们把这种行为加到基类长方形的时候,就导致了正方形无法继承这种行为。我们“强行”把正方形从长方形继承过来,就造成无法达到预期的结果。鸵鸟非鸟,能飞是鸟的特性,但鸵鸟是不能飞的,我们强行将其归为鸟类,最终导致代码出错。
所有子类的行为功能必须和其父类持一致,如果子类达不到这一点,那么必然违反里氏替换原则。在实际的开发过程中,不正确的派生关系是非常有害的。伴随着软件开发规模的扩大,参与的开发人员也越来越多,每个人都在使用别人提供的组件,也会为别人提供组件。最终,所有人的开发的组件经过层层包装和不断组合,被集成为一个完整的系统。每个开发人员在使用别人的组件时,只需知道组件的对外裸露的接口,那就是它全部行为的集合,至于内部到底是怎么实现的,无法知道,也无须知道。所以,对于使用者而言,它只能通过接口实现自己的预期,如果组件接口提供的行为与使用者的预期不符,错误便产生了。里氏替换原则就是在设计时避免出现派生类与基类不一致的行为。