设计模式 - 浅谈里氏替换原则
里氏替换原则
原则说明: 里氏替换原则(Liskov Substitution principle)是对子类型的特别定义,派生类(子类)对象可以在程序中代替其基类(超类)对象。
为什么要遵守该原则
违反里氏替换原则会导致代码的复杂性增加,可读性降低,同时也会增加代码修改的难度。当一个子类不能完全替代其父类时,这就增加了出错的可能性。
按我自己遇到的场景去理解,我调用了一个计算面积的方法,而子类无法完全替代超类,导致一个Bug的产生。下面就是一个简单的案例。
用一个简单的例子说明
假定我们有两个类,一个是矩形 Rectangle
,一个是正方形 Square
继承矩形 Rectangle
。
public class Rectangle {
private int width;
private int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return this.width * this.height;
}
}
public class Square extends Rectangle {
@Override
public void setWidth(int width) {
super.setWidth(width);
super.setHeight(width);
}
@Override
public void setHeight(int height) {
super.setWidth(height);
super.setHeight(height);
}
}
有个计算面积的方法。如果你传入一个矩形对象,这个函数会打印20,这是正确的。但是如果你传入一个正方形对象,由于正方形的宽度和高度总是相等的,所以无论你怎么设置,它们总是会被设置为相同的值,这个函数会打印16或25,这就是错误的。
public void calculateArea(Rectangle r) {
r.setWidth(5);
r.setHeight(4);
System.out.println(r.getArea());
}
遵守里氏替换原则,因为正方形类不能完全替代矩形类。我们应该将正方形类从矩形类中分离出来,使它们成为两个独立的类:
public class Rectangle {
private int width;
private int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return this.width * this.height;
}
}
public class Square {
private int side;
public void setSide(int side) {
this.side = side;
}
public int getArea() {
return this.side * this.side;
}
}
符合里氏替换原则的继承
如果子类可以完全替代父类的行为,那么这个继承就是符合里氏替换原则的。
例如,有一个动物类和一个狗类,动物类的职责是吃,狗类的职责也是吃,那么狗类就可以完全替代动物类的职责,这个继承就是符合里氏替换原则的。
public class Animal {
public void eat() {
System.out.println("Animal is eating");
}
}
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("Dog is eating");
}
}
如何避免违反里氏替换原则
- 从行为出发来设计。在做抽象或设计时,不只是要从模型概念出发,还要从行为出发。
- 明确职责,确保子类可以完全替代父类的职责
- 契约设计,时刻保持派生类与基类的契约不被破坏。