定义:继承必须确保超类所拥有的性质在子类中仍然成立。(任何基类可以出现的地方,子类一定可以出现)
超类:已经存在的,被继承的类称为超类,基类或父类。
子类:新创建的,继承的类称为子类,派生类或孩子类。
里氏替换原则主要阐述了有关继承的一些原则。也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。
里氏替换原则是继承复用的基础,它反映了基类与子类之间的关系,是对开闭原则的补充(开闭原则通过接口或抽象类实现,里氏替换原则阐述有关继承的原则及其原理),是对实现抽象化的具体步骤的规范。
实现方法:
里氏替换原则通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。
换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类中非抽象的方法。(父类就是将子类共有的功能抽取到父类中,从而提高代码的复用性,在子类中只需定义其特有的功能。)
违背里氏替换原则会出现的问题:如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。
违背里氏替换原则的修正方法:如果程序违背了里氏替换原则,则继承类的对象在基类出现的地方会出现运行错误(下面的案例中会体现)。这时的修正方法是:取消原来的继承关系,重新设计它们之间的关系。
现实生活中的案例:新西兰的几维鸟从生物学的角度来划分,它属于鸟类;但从类的继承关系来看,由于几维鸟的翅膀退化无法飞行,不能继承鸟会飞的功能,所以不能定义为“鸟”的子类。
应用:下面介绍里氏替换原则中经典的一个例子“正方形不是长方形”。
分析:在数学领域里,正方形无疑是长方形,它是一个长宽相等的长方形。所以,我们开发的一个与几何图形相关的软件系统,就可以顺理成章的让正方形继承自长方形。
1.长方形类(包含长(length)和宽(width)两个成员变量和各自的set、get方法)
public class Rectangle {
//长
private double length;
//宽
private double width;
//对应的set和get方法
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
}
2.正方形类(继承自长方形类,重写父类中的set方法)
public class Square extends Rectangle{
//重写父类设置长和宽的方法(正方形的长和宽一样)
@Override
public void setLength(double length) {
super.setLength(length);
super.setWidth(length);
}
@Override
public void setWidth(double width) {
super.setWidth(width);
super.setLength(width);
}
}
3.测试类(包含主方法、扩宽方法和打印长方形长和宽的方法)
public class RectangleDemo {
//主方法
public static void main(String[] args) {
//创建长方形对象
Rectangle r = new Rectangle();
//设置长和宽
r.setLength(20);
r.setWidth(10);
//拓宽操作
resize(r);
printLengthAndWidth(r);
System.out.println("================");
//创建正方形对象
Square s = new Square();
//设置长和宽(因为长和宽一样,所以只设置一个就可以)
s.setLength(10);
//拓宽操作
resize(s);
printLengthAndWidth(s);
}
//扩宽方法
public static void resize(Rectangle rectangle){
//判断:如果宽比长小,进行拓宽操作,直至宽大于长
while (rectangle.getWidth()<=rectangle.getLength()){
rectangle.setWidth(rectangle.getWidth()+1);
}
}
//打印方法,打印长方形的长和宽
public static void printLengthAndWidth(Rectangle rectangle){
System.out.println(rectangle.getLength());
System.out.println(rectangle.getWidth());
}
}
4.运行结果
运行结果分析:根据运行结果可以看到,如果把一个普通长方形作为参数传入resize方法,就会看到长方形宽度逐渐增长的效果。当宽度大于长度时,代码就会停止,这种行为的结果符合我们的预期;假如我们把一个正方形作为参数传入resize方法,就会看到正方形的宽度和长度都在不断增长,代码会一直运行下去,直至系统产生溢出错误。
我们由此可得出结论:在resize方法中,Rectangle类型的参数是不能被Square类型的参数所代替的,如果进行了替换就得不到预期结果。因此,Square类和Rectangle类之间的继承关系违反了里氏替换原则(子类重写了父类中非抽象的方法。按照里氏替换原则,基类可以使用的地方,子类也可以使用,但resize方法长方形可以使用,正方形不可以使用),它们之间的继承关系不成立,正方形不是长方形。
改进(修正)方法:取消原来的继承关系,重新设计它们之间的关系。我们定义长方形和正方形更一般的父类,如四边形类。我们抽象出来一个四边形接口(Quadrilateral),让Rectangle类和Square类实现Quadrilateral接口。
1.四边形接口(Quadrilateral,包含获取长和宽的抽象方法)
public interface Quadrilateral {
//获取长
double getLength();
//获取宽
double getWidth();
}
2.长方形类,实现四边形接口
public class Rectangle implements Quadrilateral{
private double length;
private double width;
@Override
public double getLength() {
return length;
}
@Override
public double getWidth() {
return width;
}
public void setLength(double length) {
this.length = length;
}
public void setWidth(double width) {
this.width = width;
}
}
3.正方形类,实现四边形接口
public class Square implements Quadrilateral{
//边长
private double side;
//正方形长和宽相等,所以都返回边长side
@Override
public double getLength() {
return side;
}
@Override
public double getWidth() {
return side;
}
//get和set方法
public double getSide() {
return side;
}
public void setSide(double side) {
this.side = side;
}
}
4.测试类(包含主方法、扩宽方法和打印四边形长和宽的方法。但在主方法中正方形对象不能调用扩宽方法,打印方法参数类型变为Quadrilateral类型)
public class RectangleDemo {
public static void main(String[] args) {
//创建长方形对象
Rectangle r = new Rectangle();
//设置长和宽
r.setLength(20);
r.setWidth(10);
//拓宽操作
resize(r);
printLengthAndWidth(r);
System.out.println("================");
//创建正方形对象
Square s = new Square();
//设置长和宽(因为长和宽一样,所以只设置一个就可以)
s.setSide(10);
printLengthAndWidth(s);
}
//扩宽方法(只适用于长方形,所以参数类型是Rectangle)
public static void resize(Rectangle rectangle){
//判断如果宽比长小,进行拓宽操作
while (rectangle.getWidth()<=rectangle.getLength()){
rectangle.setWidth(rectangle.getWidth()+1);
}
}
//打印方法(适用于长方形和正方形,所以参数类型是Quadrilateral)
public static void printLengthAndWidth(Quadrilateral quadrilateral){
System.out.println(quadrilateral.getLength());
System.out.println(quadrilateral.getWidth());
}
}
5.运行结果
{{ cmt.username }}
{{ cmt.content }}
{{ cmt.commentDate | formatDate('YYYY.MM.DD hh:mm') }}