concept
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
所有引用基类的地方必须能透明地使用其子类的对象
analyse
- 里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法,因为这样可能会改变父类原有的行为。
- 任何基类可以出现的地方,子类一定可以出现,也就是说,如果我们把代码中使用基类的地方用它的子类所代替,代码还能正常工作。
- 子类必须完全实现父类的方法,如果子类不能完整地实现父类的方法,或者子类出现在基类出现的地方发生运行错误,应该重新设计它们之间的关系,如:父类和子类都继承一个更通用的基类、原有的继承关系去掉,采用依赖、聚合、组合等关系代替…
example
这里列举最经典的一个例子正方形 【正方形是矩形的一种特殊情况,即四边相等的矩形】 和矩形 【有一个角是直角的平行四边形叫做矩】
/**
* 矩形有两个属性,长(length)和宽(breadth)
*/
class Rectangle {
private int length;
private int breadth;
public int getLength() { return length;}
public void setLength(int length) {this.length = length;}
public int getBreadth() {return breadth;}
public void setBreadth(int breadth) {this.breadth = breadth;}
public int getArea() {return this.length * this.breadth;}
}
因为在数理中,正方形是一种特殊的矩形且具有矩形全部特性,所以正方形Square
继承矩形Rectangle
,而正方形Square
的四边都相等,所以我们重写了父类Rectangle
的setLength
、setBreadth
方法,代码如下
/**
* 正方形
*/
class Square extends Rectangle {
@Override
public void setBreadth(int breadth) {
super.setLength(breadth);
super.setBreadth(breadth);
}
@Override
public void setLength(int length) {
super.setLength(length);
super.setBreadth(length);
}
}
现在,无论谁设置 Square
对象的 Width
,它的 Height
也会相应跟着变化。而当设置 Height
时,Width
也同样会改变。这样做之后,Square
对象仍然是一个看起来很合理的数学中的正方形。
public class Client{
public static void main (String args[]) {
Rectangle rectangle = new Rectangle();
rectangle.setLength(10);
rectangle.setBreadth(5);
System.out.println(rectangle.getClass().getSimpleName()+" area is" + rectangle.getArea());
}
}
输出结果为 Rectangle area is 50
根据定义 【所有引用基类的地方必须能透明地使用其子类的对象】,这里将基类Rectangle
替换为子类Square
public class Client{
public static void main (String args[]) {
Rectangle rectangle = new Square();
rectangle.setLength(10);
rectangle.setBreadth(5);
System.out.println(rectangle.getClass().getSimpleName()+" area is" + rectangle.getArea());
}
}
输出结果为 Rectangle area is 25
所以这种情况不满足里氏替换原则,显然在java所构建的世界中,现实世界的理论有时候是行不通的.这里断开Square
和Rectangle
的联系,重新构建一个Square
独有的对象,实现如下
class Square {
private int length;
public int getLength() { return length;}
public void setLength(int length) {this.length = length;}
}
public class Client{
public static void main (String args[]) {
Square square = new Square();
square.setLength(10);
square.setBreadth(5);
System.out.println(square.getClass().getSimpleName()+" area is" + square.getArea());
}
}
契约式设计(Design by Contract)
Bertrand Meyer 在 1988 年阐述了 LSP 原则与契约式设计之间的关系
对于类的一个方法,都有一个前提条件以及一个后续条件
- 前提条件说明方法接受什么样的参数数据等,只有前提条件得到满足时,这个方法才能被调用;
- 后续条件用来说明这个方法完成时的状态,如果一个方法的执行会导致这个方法的后续条件不成立,那么这个方法也不应该正常返回。
现在把前提条件以及后续条件应用到继承子类中,子类方法应该满足:
- 前提条件不强于基类
- 后续条件不弱于基类.
开放封闭原则(Open Closed Principle)是许多面向对象设计启示思想的核心。符合该原则的应用程序在可维护性、可重用性和鲁棒性等方面会表现的更好。里氏替换原则(Liskov Substitution Principle)则是实现 OCP 原则的重要方式。只有当衍生类能够完全替代它们的基类时,使用基类的函数才能够被安全的重用,然后衍生类也可以被放心的修改了。