里氏替换原则 (Liskov Substitution Principle)
定义:派生类(子类)对象可以在程序中代替其基类(超类)对象
- 简单的理解为一个软件实体如果使用的是一个父类,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。也就是说,软件里面,把父类都替换成它的子类,程序的行为没有变化。
主要体现在下面四个方面:
- 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。
- 子类中可以增加自己特有的方法。
- 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
- 长方形和正方形的例子:
在下边的代码中我们定义了一个长方形和一个继承自长方形的正方形,看着是非常符合逻辑的,但是当我们调用 Test 类中的 resize 方法时,长方形是可以的,但是正方形就会一直增大,一直 long 溢出。但是我们按照我们的里氏替换原则,父类可以的地方,换成子类一定也可以,这个不符合里氏替换原则的。
/*
* 长方形
*/
public class Rectangle {
protected long width;
protected long height;
public void setWidth(long width) {
this.width = width;
}
public long getWidth() {
return this.width;
}
public void setHeight(long height) {
this.height = height;
}
public long getHeight() {
return this.height;
}
}
/*
* 正方形
*/
public class Square extends Rectangle {
public void setWidth(long width) {
this.height = width;
this.width = width;
}
public long getWidth() {
return width;
}
public void setHeight(long height) {
this.height = height;
this.width = height;
}
public long getHeight() {
return height;
}
}
/*
* 测试
*/
public class Test{
/**
* 长方形的长不短的增加直到超过宽
*/
public void resize(Rectangle r){
while (r.getHeight() <= r.getWidth() ) {
r.setHeight(r.getHeight() + 1);
}
}
}
-
正确做法:
构造一个抽象的四边形类,把长方形和正方形共同的行为放到这个四边形类里面,让长方形和正方形都是它的子类,问题就OK了。对于长方形和正方形,取width和height是它们共同的行为,但是给width和height赋值,两者行为不同,因此,这个抽象的四边形的类只有取值方法,没有赋值方法。上面的例子中那个方法只会适用于不同的子类,LSP也就不会被破坏。 -
总结:使用LSP,使得程序具有更多的可维护性、可重用性以及健壮性。而LSP是使OCP成为可能的主要原则之一,子类型的可替换性才使得基类类型的模块在无需修改的情况下可以扩展。
继承包含这样一层含义:父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。