1. 背景
有一个功能p1,由类A完成,现在需要将功能p1进行扩展,扩展后的功能为p3,p3由原功能p1和新功能p2组成,而新功能p3和p2均由类A的子类B来完成,子类B在完成新功能p2的同时,可能会导致原有的功能p1故障。
2. 定义
所有引用基类的地方能透明的使用其子类对象进行替代。
3. 对应的解决方案
使用继承时,父类已经实现好了的方法(非抽象方法),实际上是有一系列规范和契约的,虽然不强制子类必须遵循,但如果子类对这些非抽象方法进行重写、重载后,会造成整个继承体系被破坏。而里氏替换原则也正是表达了这层含义。
4. 补充对继承的理解
继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加了对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能会产生故障。
5. 案例
需求一:完成两个数相减的功能,由类A完成。
public class A
{
/// <summary>
/// 两数相加
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public int func1(int a, int b)
{
return a - b;
}
/// <summary>
/// 两数相乘
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public int func3(int a, int b)
{
return a * b;
}
}
需求二:完成两个数相加,然后再加100的功能,由类A的子类类B完成。
public class B:A
{
public int func1(int a, int b)
{
return a + b;
}
public int func2(int a, int b)
{
return func1(a, b) + ;
}
}
下面我们对上述写的代码进行测试。
(1). 用类A测试两个数相减,如:100-50
(2). 用类B测试两个数相减(因为类B继承了类A),如:100-50
(3). 用类B测试两个数相加后再加100的功能,如:100+50+100
public static void show()
{
//需求1:完成两个数相减的功能,由类A来完成
A a = new A();
Console.WriteLine("100-50={0}", a.func1(, )); //50 //需求2:完成两个数相加功能,然后再加100,由类A的子类B来完成
B b = new B();
Console.WriteLine("100-50={0}", b.func1(, )); //150,因为子类重写了fun1,则隐藏了父类中fun1原有的方法 (错误)
Console.WriteLine("100和50相加,然后再减掉100,结果为:{0}", b.func2(, )); // }
结果:
我们会发现, 用类B测试两个数相减的功能出错了,竟然变成两数相加的功能了,原因是类B中声明了和其父类相同的方法名的方法,即隐藏了父类原有的方法,违背了里氏替换原则。
6. 里氏替换原则的深刻理解
子类可以扩展父类没有的功能,但是不能改变父类的功能
a:子类可以实现父类的抽象方法,但不能重写父类的非抽象方法。
b:子类可以增加自己特有的方法。
c:子类重载父类方法时,其方法的形参要比父类方法的形参更宽松。
d:子类实现父类抽象方法时,其方法的返回值要比父类方法的更严格。
7. 正宗的里氏替换原则的用法
public static void show()
{ A aa = new B();
Console.WriteLine("100*50={0}",aa.func3(, )); //5000 正宗的里氏替换原则
//Console.WriteLine(aa.func2(100, 50)); //代码报错,父类不能调用子类扩展的方法
}
8. 违背里氏替换原则的后果
出错率会增高