里式替换原则(Liskov Substitution Principle),缩写为LSP,这个原则最早是在1986年由麻省理工学院计算机科学实验室的Barbara Liskov提出,她是这么描述这条原则的:
If S is a subtype of T, then objects of type T may be replaced with objects of type S, without breaking the program。
在1996年,Robert C.Martin(罗伯特C.马丁,鲍勃大叔)在他的SOLID(S单一职责 O开闭原则 L里式替换原则 I接口隔离原则 D依赖倒置原则)原则中,重新描述了这个原则:
Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it。
综合两者描述,我们将这条原则用中文描述出来为:程序中的对象可以在不改变程序正确性的前提下被它的子类所替换,即:子类可以替换任何父类能够出现的地方,并且经过替换后,代码还能正确工作。
根据LSP的定义,如果在程序中出现使用instanceof、强制类型转换或者函数覆盖,很可能意味着是对LSP的破坏。
里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。根据上述理解,对里氏替换原则的定义可以总结如下:
以鸵鸟是不是鸟为例,鸵鸟是一种鸟,但是不会飞。
1.我们创建鸟类,有个飞行的方法,然后鸵鸟和麻雀分别继承鸟类,但是鸵鸟由于不会飞,所以需要重写父类的飞行方法。
package will.tools.design.lsp.a;
public class Birds {
public void fly() {
System.out.println("I am flying");
}
}
package will.tools.design.lsp.a;
public class Sparrow extends Birds {
@Override
public void fly() {
super.fly();
}
}
package will.tools.design.lsp.a;
public class Ostrich extends Birds {
@Override
public void fly() {
System.out.println("i can not fly");
}
}
这时我们看到,在鸵鸟的类中,我们重写了飞行方法。我们测试一下,
public static void main(String[] args){
Birds birds = new Birds();
birds.fly();
Birds sparrow = new Sparrow();
sparrow.fly();
Birds ostrich = new Ostrich();
ostrich.fly();
}
I am flying
I am flying
i can not fly
我们发现,创建父类对象new Birds的地方,我们用Sparrow来替换,没有问题,但是用Ostrich来替换,运行结果就已经变了。这其实就是我们设计鸵鸟类的时候,继承鸟类这里已经违反了里式替换原则。
怎么修改呢,一种方式我们认为鸵鸟和鸟还是有继承关系存在的(鸵鸟是鸟),把无法继承的飞行的行为抽象为接口,Birds类中不再有飞行方法。麻雀和鸵鸟都继承鸟的基础信息,但是根据业务规则,麻雀实现飞行的接口,鸵鸟不实现飞行接口。另一种方式是,我们认为鸵鸟和鸟不再有继承关系(鸵鸟不是鸟),这是我们抽象一个动物类,有一个move方法,鸟类和鸵鸟都继承动物类(鸟类的移动是飞,鸵鸟的移动是跑,但都是move),麻雀继承鸟类。
我们以第二种方式类修改代码如下
package will.tools.design.lsp.b;
public class Animal {
public void move() {
System.out.println("I am moving");
}
}
package will.tools.design.lsp.b;
public class Birds extends Animal{
@Override
public void move() {
super.move();
}
}
package will.tools.design.lsp.b;
public class Ostrich extends Animal {
@Override
public void move() {
super.move();
}
}
package will.tools.design.lsp.b;
public class Sparrow extends Birds{
@Override
public void move() {
super.move();
}
}
public static void main(String[] args){
Animal animal = new Animal();
animal.move();
Animal birds = new Birds();
birds.move();
Animal sparrow = new Sparrow();
sparrow.move();
Animal ostrich = new Ostrich();
ostrich.move();
}
这是我们运行验证,结果为:
I am moving
I am moving
I am moving
I am moving
这时,我们出现父类对象new Animal()的地方,使用 Birds,Sparrow,Ostrich 来替换,运行结果都是一致的,都没有问题