一、定义
里氏置换原则(Liskov Substitution Principle),简称LSP
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
所有引用基类的地方必须能够透明的使用其子类对象。
所有引用基类的地方必须能透明地使用其子类的对象,通俗的来讲就是父类能出现的地方子类就可以出现,但是反过来就不行了。子类可以扩展父类的功能,但不能改变父类原有的功能。只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
里氏替换原则为良好的继承定义了一个规范;
在类中调用其他类时务必要使用父类或者接口,如果不能使用父类或者接口,则说明类的设计已经违背了LSP原则;
我们在做系统设计时,经常会定义一个接口或者抽象类,然后编码实现,调用类则直接传入接口或者抽象类,不关心具体实现;
如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承;
在项目中采用里氏替换原则时,尽量避免子类的个性,一旦子类有个性这个子类和父类的关系就很难调和了。
定义包含四层意思:
1、子类可以实现父的抽象方法,但不能覆写父类的非抽象方法。 父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。
如下示例:
定义一个抽象类:
package com.dp.lsp;
/**
* 定义一个图形抽象类
* @author lijbe
*
*/
public abstract class Figure {
private String figureName;
public void setFigureName(String figureName){
this.figureName = figureName;
}
public String getFigureName() {
return figureName;
}
/**
* 打印图形名称
*/
public abstract void printFigureName();
}
定义两个实现类:
package com.dp.lsp;
public class Rectangle extends Figure{
@Override
public void printFigureName() {
this.setFigureName("我是长方形");
System.out.println(this.getFigureName());
}
}
圆形:
package com.dp.lsp;
public class Roundness extends Figure {
@Override
public void printFigureName() {
this.setFigureName("我是圆形");
System.out.println(this.getFigureName());
}
}
定义一个调用类:
package com.dp.lsp;
public class ClientLsp {
public static void printFigureName(Figure figure){
figure.printFigureName();
}
public static void main(String[] args) {
Figure rectangle = new Rectangle();
ClientLsp.printFigureName(rectangle);
Figure roundness = new Roundness();
ClientLsp.printFigureName(roundness);
}
}
输出结果:
我是长方形
我是圆形
如果我们把父类的setFigureName方法在Roundness类中重写了,再试一下。
package com.dp.lsp;
public class Roundness extends Figure {
@Override
public void printFigureName() {
this.setFigureName("我是圆形");
System.out.println(this.getFigureName());
}
@Override
public void setFigureName(String figureName){
// do nothing
}
}
输出结果:
我是长方形
null
这就破坏了原有的继承规则,不符合LSP原则。
2、 子类中可以增加自己特有的方法。
package com.dp.lsp;
public class Roundness extends Figure {
@Override
public void printFigureName() {
this.setFigureName("我是圆形");
System.out.println(this.getFigureName());
}
@Override
public void setFigureName(String figureName){
// do nothing
}
public double calculateArea(double radius){
double pi = 3.14;
return pi*radius*radius;
}
}
3) 覆写或实现父类的方法时,输入参数可以被放大。(覆写指的覆写一个正常方法并重写,实现指的是实现接口或者抽象方法)
4) 覆写或实现父类的方法时输出结果可以被缩小(若放大,还能用子类替换父类吗?)
总之,LSP原则核心就是子类可以替换父类,而父类不可以替换子类。因为子类包含了父类的所有特性,而父类则不会包含子类的所有特性,因为子类有自己扩展的特性。另外一层原则是:子类不要轻易的改变(覆写)父类实现的方法,因为这也会破坏继承规则。