Java设计模式-软件设计原则-里氏替换原则

岳庆锦

发布于 2022.03.05 19:20 阅读 1625 评论 0

里氏替换原则

 定义:继承必须确保超类所拥有的性质在子类中仍然成立。(任何基类可以出现的地方,子类一定可以出现)

 超类:已经存在的,被继承的类称为超类,基类或父类。

 子类:新创建的,继承的类称为子类,派生类或孩子类。

 里氏替换原则主要阐述了有关继承的一些原则。也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理。

 里氏替换原则是继承复用的基础,它反映了基类与子类之间的关系,是对开闭原则的补充(开闭原则通过接口或抽象类实现,里氏替换原则阐述有关继承的原则及其原理),是对实现抽象化的具体步骤的规范。

 实现方法

 里氏替换原则通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。

 换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类中非抽象的方法。(父类就是将子类共有的功能抽取到父类中,从而提高代码的复用性,在子类中只需定义其特有的功能。)

 违背里氏替换原则会出现的问题:如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。

 违背里氏替换原则的修正方法:如果程序违背了里氏替换原则,则继承类的对象在基类出现的地方会出现运行错误(下面的案例中会体现)。这时的修正方法是:取消原来的继承关系,重新设计它们之间的关系。

 现实生活中的案例:新西兰的几维鸟从生物学的角度来划分,它属于鸟类;但从类的继承关系来看,由于几维鸟的翅膀退化无法飞行,不能继承鸟会飞的功能,所以不能定义为“鸟”的子类。

 应用:下面介绍里氏替换原则中经典的一个例子“正方形不是长方形”。

 分析:在数学领域里,正方形无疑是长方形,它是一个长宽相等的长方形。所以,我们开发的一个与几何图形相关的软件系统,就可以顺理成章的让正方形继承自长方形。

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.运行结果