里氏代换原则是什么
从“开-闭”原则中可以看出面向对象设计的重要原则使创建抽象化,并且从抽象化引出具体化。具体化可以给出不同的版本,每一个版本都给出不同的实现。
从抽象化到具体化的引出,要使用继承关系和里氏代换原则。
里氏代换原则的严格表达是:
如果对每一个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都代换为 o2 时,程序 P 的行为没有变化,那么类型 T2 是类型 T1 的子类型。
换言之,一个软件实体如果使用的是一个基类的话,那么一定适用于其子类,而且它根本不能察觉出基类对象和子类对象的区别。
比如,假设有两个类,一个是 Base 类,另一个是 Derived 类,并且 Derived 类是 Base 类的子类。那么一个方法如果可以接受一个基类对象 b 的话:
method(Base b)
那么它必然可以接受一个子类对象 d,也即可以有 method1(d)。
里氏代换原则是继承复用的基石。只有当衍生类可以替换掉基类,软件功能不会受到影响时,基类才能真正被复用,而衍生类也才能够在基类的基础上增加新的行为。
但是,反过来的代换则不成立,即如果一个软件实体使用的是一个子类的话,那么它不一定适用于基类。如果一个方法 method2 接受子类对象为参数的话:
method2(Derived d)
那么一般而言不可以有 method2(b)。
如何做到里氏代换原则
Java 对里氏代换的支持
在编译时期,Java 语言编译器会检查一个程序是否符合里氏代换,当然这是一个无关实现的,纯语法意义上的检查。
里氏代换要求凡是基类型使用的地方,子类型一定适用,因此子类必须具备基类型的全部接口。或者说,子类型的接口必须包括全部的基类型的接口,而且还有可能更宽。如果一个 Java 程序破坏这一条件,Java 编译器就会给出编译时期错误。
举例而言,一个基类 Base 声明了一个 public 方法 method(),那么其子类型 Sub 可否将这个方法的访问权限从 public 改换成为 private 呢?换言之,子类型可否使用一个低访问权限的方法 private method() 将超类型的方法 public method() 置换(override)掉呢?
客户端完全有可能调用超类型的公开方法,如果以子类型代之,这个方法却变成了私有的,客户端不能调用,显然这是违反里氏代换法则的,Java 编译器不会让这样的程序通过。
UML 类图

示例代码
package principle;
/**
* 里氏替换
*
* @author asyyr
*/
public class Liskow {
public static void main(String[] args) {
int num1 = 11;
int num2 = 8;
System.out.println(new AA().func1(num1, num2));
System.out.println(new BB().func1(num1, num2));
CC cc = new CC();
cc.setaa(new AA());
System.out.println(cc.func1(num1, num2));
}
}
class AA {
public int func1(int num1, int num2) {
return num1 - num2;
}
}
class BB extends AA {
@Override
public int func1(int num1, int num2) {
return num1 + num2;
}
public int func2(int num1, int num2) {
return num1 * num2;
}
}
// 扩展 BB,使用组合关系依赖AA
class CC {
private AA aa;
public void setaa(AA aa) {
this.aa = aa;
}
public int func1(int num1, int num2) {
return this.aa.func1(num1, num2);
}
public int func2(int num1, int num2) {
return num1 * num2;
}
}
与其它设计模式的关系
策略模式
策略模式讲的是,如果有一组算法,那么就将每一个算法封装起来,使得它们可以互换。策略模式的简略类图如下图所示:
封装使得所有的算法可以互换,需要将所有的具体策略角色放到一个类型等级结构中,使它们拥有共同的接口,显然这种互换性依赖的是对里氏代换原则的遵守:
AbstractStrategy s = new ConcreteStrategyA();
从上面的代码片段可以看出,客户端依赖于基类类型,而变量的真实类型则是具体策略类,这是具体策略角色可以“即插即用”的关键。
合成模式
合成模式通过使用树结构描述整体与部分的关系,从而可以将单纯元素与复合元素同等看待。由于单纯元素和复合元素都是抽象元素角色的子类,因此两者都可以替代抽象元素出现在任何地方,显然,里氏代换原则是合成模式能够成立的基础。合成模式的简略类图如下图所示:
代理模式
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。代理模式能够成立的关键,就在于代理模式与真实主题模式都是抽象主题角色的子类。客户端只知道抽象主题,而代理主题可以替代抽象主题出现在任何需要的地方,而将真实主题隐藏在幕后。显然,里氏代换原则是代理模式能够成立的基础。代理模式的简略类图如下图所示: