软件构造-LSP替换原则(Liskov Substitution Principle)

  1. 子类型可以增加方法,但不可删
  2. 子类型需要实现抽象类型中的所有未实现方法
  3. 子类型中重写的方法必须有相同或子类型的返回值或者符合co-variance(协变)的参数
  4. 子类型中重写的方法必须使用同样类型的参数或者符合contra-variance(反协变)的参数
  5. 子类型中重写的方法不能抛出额外的异常

Barbara Liskov教授提出,她是美国第一位计算机科学方向的女博士,在2008年获得了图灵奖。

在一个非常经典的例子中,当我们实现了一个长方形的类:

 1  public class Rectangle
 2   {
 3     private double _width;
 4     private double _height;
 5 
 6     public void SetWidth(double w) { _width = w; }
 7     public void SetHeight(double w) { _height = w; }
 8     public double GetWidth() { return _width; }
 9     public double GetHeight() { return _height; }
10   }

在常识中,我们都认为正方形属于长方形,那么如果我们通过继承长方形的类,来实现一个正方形的类,是否会出现问题呢?
首先我们会发现对于正方形而言,长和宽是相等的,_width和_height变量的值不会不同。所以在子类中SetWidth和SetHeight的两个方法需要重写:
 1   public class Square : Rectangle
 2   {
 3     public void SetWidth(double w)
 4     {
 5       base.SetWidth(w);
 6       base.SetHeight(w);
 7     }
 8     public void SetHeight(double w)
 9     {
10       base.SetWidth(w);
11       base.SetHeight(w);
12     }
13   }

但现在看下下面这个方法:

1   void f(Rectangle r)
2   {
3     r.SetWidth(32); // calls Rectangle::SetWidth
4   }

如果我们传递一个 Square 对象的引用到这个方法中,则 Square 对象将被损坏,因为它的 Height 将不会被更改。这里明确地违背了 LSP 原则,此函数在衍生对象为参数的条件下无法正常工作。编译器到底是调用父类中的SetWidth()还是SetWidth(),这里会发生冲突。正确的做法应该是在父类中不要确切地实现SetWidth和SetHeight方法,而是编写成抽象方法。

实际上,在面向对象的编程中,一个正方形的对象并不完全是一个长方形的对象,面向对象的编程强调每个对象独特的行为(behavior),一个正方形对象的行为和一个长方形对象的行为是有很大程度的不同的,所以最好不要单纯通过数学或者逻辑知识上的归属关系来设计对象之间的继承关系。

 

posted @ 2022-06-06 16:52  DDDaily  阅读(42)  评论(0编辑  收藏  举报