里氏替换原则
里氏替换原则的由来
里氏替换原则(Liskov Substitution Principle LSP)是由麻省理工学院计算机科学实验室的Liskov(里斯科瓦)女生提出的。
在1987年的OOPSLA大会上发表的一篇文《DataAbstractionand Hierarchy》里面提出来的,主要阐述了有关继承的一些规则, 就是什么时候使用继承,什么时候不应该使用继承,以及其中蕴含的原理。
里氏替换原则的定义
第一种定义:If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T. 如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。 第二种定义:Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it. 所有引用基类的地方必须能透明地使用其子类的对象
里氏替换原则的含义
1.子类必须完全实现父类的方法 2.子类可以增加自己特有的方法 3.覆盖或实现父类的方法时输入参数可以被放大 4.覆盖或实现父类的方法时输出结果可以被缩小
里氏替换原则的例子
A类实现两个数相减的功能
1 public class A 2 { 3 public int fun1(int a, int b) 4 { 5 return a - b; 6 } 7 } 8 class Program 9 { 10 static void Main(string[] args) 11 { 12 A a = new A(); 13 Console.WriteLine("100-50="+a.fun1(100,50)); 14 Console.WriteLine("100-80="+a.fun1(100, 80)); 15 Console.Read(); 16 } 17 }
显示结果
后来,我们需要增加一个新的功能:完成两数相加,然后再与100求和,由类B来负责
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 B b = new B(); 6 Console.WriteLine("100-50="+b.fun1(100,50)); 7 Console.WriteLine("100-80="+b.fun1(100, 80)); 8 Console.WriteLine("100+20+100="+b.fun2(100, 20)); 9 Console.Read(); 10 } 11 } 12 13 public class B : A 14 { 15 public int fun1(int a, int b) 16 { 17 return a + b; 18 } 19 20 public int fun2(int a,int b) 21 { 22 return fun1(a, b) + 100; 23 } 24 }
显示结果
我们发现原本运行正常的相减功能发生了错误。
原因就是类B在给方法起名时无意中重写了父类的方法,造成所有运行相减功能的代码全部调用了类B重写后的方法,造成原本运行正常的功能出现了错误。
类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。
如果非要重写父类的方法,比较通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合,组合等关系代替。
我的理解
里氏替换原则是为了保证继承的正确性而存在的。
根本要求就是父类出现的地方子类能够替换,如果不能替换就是不合理的继承。
比如鸵鸟不是鸟的问题,一般来说鸟都会飞的,鸵鸟可以继承鸟,但他不会飞。当父类鸟出现时要用到飞的方法时,鸵鸟是不能替换它的,因为他不会飞,所以他不是鸟。
这时我们可以针对接口和抽象来编程,把飞的方法放到一个接口中。父类中凡是已经实现好的方法(相对于抽象方法而言),
实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,
但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。
评论