首页 > 代码库 > 设计模式六大原则之里氏替换原则

设计模式六大原则之里氏替换原则

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所表述的就是在同一个继承体系中的对象应该有共同的行为特征。我们在设计对象时是按照行为进行分类的,只有行为一致的对象才能抽象出一个类来。设置长方形的长度的时候,它的宽度保持不变,设置宽度的时候,长度保持不变。正方形的行为:设置正方形的长度的时候,宽度随之改变;设置宽度的时候,长度随之改变。所以,如果我们把这种行为加到基类长方形的时候,就导致了正方形无法继承这种行为。我们“强行”把正方形从长方形继承过来,就造成无法达到预期的结果。鸵鸟非鸟,能飞是鸟的特性,但鸵鸟是不能飞的,我们强行将其归为鸟类,最终导致代码出错。

所有子类的行为功能必须和其父类持一致,如果子类达不到这一点,那么必然违反里氏替换原则。在实际的开发过程中,不正确的派生关系是非常有害的。伴随着软件开发规模的扩大,参与的开发人员也越来越多,每个人都在使用别人提供的组件,也会为别人提供组件。最终,所有人的开发的组件经过层层包装和不断组合,被集成为一个完整的系统。每个开发人员在使用别人的组件时,只需知道组件的对外裸露的接口,那就是它全部行为的集合,至于内部到底是怎么实现的,无法知道,也无须知道。所以,对于使用者而言,它只能通过接口实现自己的预期,如果组件接口提供的行为与使用者的预期不符,错误便产生了。里氏替换原则就是在设计时避免出现派生类与基类不一致的行为。

 

设计模式六大原则之里氏替换原则