00_6面向对象设计原则-里氏替换原则

定义:里氏替换原则指如果对每一个类型为T1和对象O1,都有类型T2的对象O2,使得以T1定义的所有程序P在所有对象O1都替换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
 
里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
 
根据上述理解,对里氏替换原则的定义可以总结如下:
  1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
  2. 子类中可以增加自己特有的方法
  3. 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松
  4. 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的的输出/返回值)要比父类的方法更严格或相等
 
下面我们来描述一个经典的业务场景,用正方形、巨型和四边形的关系说明里氏替换原则,我们都知道正方形是一个特殊的长方形,那么可以创建一个长方形的父类Rectangle类,代码如下:
package org.test.design.principle.lsp;

public class Rectangle {
    private long height;
    private 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;
    }
}
Square.java
package org.test.design.principle.lsp;

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 long getWidth(){
        return getLength();
    }
    @Override
    public void setWidth(long width) {
        setLength(width);
    }

    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());
    }
}

测试:

public static void main(String[] args) {
    Rectangle rectangle = new Rectangle();
    rectangle.setWidth(20);
    rectangle.setHeight(10);
    resize(rectangle);
}
输出:
Width:20,Height:11
Width:20,Height:12
Width:20,Height:13
Width:20,Height:14
Width:20,Height:15
Width:20,Height:16
Width:20,Height:17
Width:20,Height:18
Width:20,Height:19
Width:20,Height:20
Width:20,Height:21
Resize End,Width:20,Height:21
 
看到结果可知,高比宽大,这在长方形中是一种非常正常的情况,在来看下面的代码,把长方形替换成它的子类正方形,修改客户端测试代码:
public static void main(String[] args) {
        Square square = new Square();
        square.setLength(10);
        resize(square);
}
此时,运行出现死循环,违背了里氏替换原则,在将父类替换为子类后,程序运行结果没有达到预期。因此,代码设计存在一定的风险的。里氏替换原则只存在于父类与子类之间,约束继承泛滥。再来创建一个基于长方形的共同的抽象--四边形接口,代码如下:
package org.test.design.principle.lsp;

public interface QuardRangle {
    long getWidth();
    long getHeight();
}
在来看一下长方形类的V2版本:
package org.test.design.principle.lsp;

public class RectangleV2 implements QuardRangle{
    private long height;
    private long width;

    public void setHeight(long height) {
        this.height = height;
    }

    public void setWidth(long width) {
        this.width = width;
    }

    @Override
    public long getWidth() {
        return width;
    }

    @Override
    public long getHeight() {
        return height;
    }
}
在看看正方形类的V2版本:
package org.test.design.principle.lsp;

/**
 * TODO
 *
 * @author Administrator
 * @version 1.0
 * @date 2021/11/17 20:55
 */
public class SquareV2 implements QuardRangle {
    private long length;

    public long getLength() {
        return length;
    }

    public void setLength(long length) {
        this.length = length;
    }

    @Override
    public long getWidth() {
        return length;
    }

    @Override
    public long getHeight() {
        return length;
    }
}
测试:
 public static void resize(RectangleV2 square) {
        while (square.getWidth() >= square.getHeight()) {
            square.setHeight(square.getHeight() + 1);
            System.out.println("Width:" + square.getWidth() + ",Height:" + square.getHeight());
        }
        System.out.println("Resize End,Width:" + square.getWidth() + ",Height:" + square.getHeight());
    }

    public static void main(String[] args) {
        RectangleV2 rectangle = new RectangleV2();
        rectangle.setWidth(20);
        rectangle.setHeight(10);
        resize(rectangle);
    }
我们在来看一下,如果把resize()方法的参数换成形QuardRangleV2类,方法内部就会报错。因为正方形已经没有了setWidth()和setHeigth()方法,所以,为了约束继承泛滥,resize()方法的参数只能用长方形RectangleV2类。
 
 此篇完。

posted @ 2021-11-18 14:15  田园码民  阅读(73)  评论(0)    收藏  举报