里氏替换原则
简单描述:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
几维鸟不是鸟
分析:鸟一般都会飞行,如燕子的飞行速度大概是每小时 120 千米。但是新西兰的几维鸟由于翅膀退化无法飞行。
假如要设计一个实例,计算这两种鸟飞行 300 千米要花费的时间。显然,拿燕子来测试这段代码,结果正确,能计算出所需要的时间;
但拿几维鸟来测试,结果会发生“除零异常”或是“无穷大”。
几维鸟类重写了鸟类的 setSpeed(double speed) 方法,这违背了里氏替换原则。
正确的做法是:取消几维鸟原来的继承关系,定义鸟和几维鸟的更一般的父类,如动物类,它们都有奔跑的能力。
几维鸟的飞行速度虽然为 0,但奔跑速度不为 0,可以计算出其奔跑 300 千米所要花费的时间。
UML类图如下。
1.新建animal类
public class Animal {
private double runSpeed;
public void setRunSpeed(double runSpeed){
this.runSpeed = runSpeed;
}
public double getRunTime(double distance){
return distance/this.runSpeed;
}
}
2.新建Bird(鸟类)及Swallow(燕子)
public class Bird extends Animal {
private double flySpeed;
public void setFlySpeed(double flySpeed){
this.flySpeed = flySpeed;
}
public double getFlyTime(double distance){
return distance/this.flySpeed;
}
}
// 燕子
public class Swallow extends Bird {
}
3.新建几维鸟
public class BrownKiwi extends Animal {
}
4.新建主类运行
Bird swallow = new Swallow();
swallow.setFlySpeed(120);
double flyTime = swallow.getFlyTime(300);
Animal brownKiwi = new BrownKiwi();
brownKiwi.setRunSpeed(50);
double runTime = brownKiwi.getRunTime(300);
System.out.println("燕子飞行时间:" + flyTime);
System.out.println("几维鸟跑步时间:" + runTime);
用正方形、矩形和四边形的关系来说明里氏替换原则。
1.新建长方形类Rectangle
public class Rectangle {
/**
* 高
*/
long height;
/**
* 宽
*/
long width;
public long getHeight() {
return height;
}
public void setHeight(long height) {
this.height = height;
}
public long getWidth() {
return width;
}
public void setWidth(long width) {
this.width = width;
}
}
2.新建正方形类
public class Square extends Rectangle {
private long length;
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
@Override
public long getHeight() {
return getLength();
}
@Override
public void setHeight(long height) {
setLength(height);
}
@Override
public void setWidth(long width) {
setLength(width);
}
@Override
public long getWidth() {
return getLength();
}
}
3.新建扩容方法
public static void resize(Rectangle rectangle) {
while (rectangle.getWidth() >= rectangle.getHeight()) {
rectangle.setHeight(rectangle.getHeight() + 1);
System.out.println("width: " + rectangle.getWidth() +
",Height: " + rectangle.getHeight());
}
System.out.println("Resize End,width:" + rectangle.getWidth() +
" ,Height:" + rectangle.getHeight());
}
4.测试
Rectangle rectangle = new Rectangle();
rectangle.setWidth(20);
rectangle.setHeight(10);
resize(rectangle);
由运行结果可知,高比宽还大,这在长方形中是一种非常正常的情况。再看下面的代码,把长方形替换成它的子类正方形,修改客户端测试代码如下
Square square = new Square();
square.setLength(10);
resize(square);
此时,运行出现了死循环,违背了里氏替换原则,在将父类替换成子类后,程序运行结果没有达到预期。
里氏替换原则只存在于父类和子类之间,约束继承泛滥。再来创建一个基于长方形与正方形共同的抽象——四边形 QuardRangle 接口,代码如下。
public interface QuardRangle {
long getWidth();
long getHeight();
}
修改长方形 Rectangle 类的代码如下。
private long height; //高
private long width; //宽
@Override
public long getHeight() {
return height;
}
public void setHeight(long height) {
this.height = height;
}
public void setWidth(long width) {
this.width = width;
}
@Override
public long getWidth() {
return width;
}
修改正方形 Square 类的代码如下。
public class Square implements QuardRangle {
private long length;
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
@Override
public long getHeight() {
return getLength();
}
@Override
public long getWidth() {
return getLength();
}
}
此时,如果把 resize() 方法的参数换成四边形 QuardRangle 类,方法内部就会报错。因为正方形已经没有了 setWidth() 和 setHeight() 方法,所以,为了约束继承泛滥,resize() 方法的参数只能用长方形 Rectangle 类。
参考资料:
1.http://c.biancheng.net/view/1324.html
代码地址:https://gitee.com/zhoujie1/design-mode-and-principle.git