设计原则之里氏替换原则详解

一、里氏替换原则定义

定义: 如果对每一个类型为T1的对象O1,都有类型为T2的对象O2,使得所有以T1定义的所有程序P在所有的对象O1都替换成O2时,程序P的行为没有发生任何变化,那么类型T2是类型T1的子类型。

通俗理解就是:子类可以扩展父类的功能,但不能改变父类原有的功能。有以下几个引申含义:
  1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  2. 子类中可以增加自己特有的方法。
  3. 当子类的方法重载父类的方法时,方法的前置条件(方法的输入,入参)要比父类的入参更宽松。
  4. 当子类的方法实现父类的方法时(重写,重载,实现抽象方法),方法的后置条件(输出、返回值)要比父类更严格或相等。
概念扩展:一个软件实体如果适用一个父类的话,那么一定适用其子类,所有引用父类的地方必须能透明的使用其子类的对象,子类对象能够替换其父类对象,而程序的逻辑不变。
里氏替换原则主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,它反映了基类与子类之间的关系,是对开闭原则的补充。
二、里氏替换原则优点
  1. 约束继承泛滥,它也是开闭原则的一种很好的体现。
  2. 提高了代码的重用性。
  3. 降低了系统的出错率。类的扩展不会给原类造成影响,降低了代码出错的范围和系统出错的概率。
  4. 加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的维护性、可扩展性,降低需求变更时引入的风险。
三、下面我们举符合和不符合的里氏替换原则的例子说明

例子描述: 先定义一个长方形类,正方形是特殊的长方形,再定义一个正方形类继承长方形类并重写长方形类中的方法,再编写一个测试类,测试类中定义一个方法,当长方形的长度大于宽度时候,高度每次加一,输出长和宽的数值。在main方法中给长方形长和宽赋初始值。发现长方形打印没问题。但如果给main方法中的正方形边长赋值,则while条件会变成永真条件,无限输出,这就违背了里氏替换原则,因为用父类执行可以,用子类执行逻辑就被改变了。

@Getter
@Setter
public class Rectangle {//长方形
    private long height;
    private long width;
}

@Setter
@Getter
public class Square extends Rectangle{//正方形
    private long length;

    @Override
    public long getHeight() {
        return getLength();
    }

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

    @Override
    public void setHeight(long height) {
       setLength(height);
    }

    @Override
    public void setWidth(long width) {
       setLength(width);
    }
}

public class SimpleTest {
    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());
        }
    }
    public static void main(String[] args) {
        Rectangle rectangle = new Rectangle();
        rectangle.setWidth(10);
        rectangle.setHeight(5);
        resize(rectangle);
        
        Square square = new Square();
        square.setLength(10);
        resize(square);
    }
}

我们怎么修改代码使它遵循里氏替换原则呢,我们可以抽象出来一个四边形接口,长方形和正方形分别实现接口。

@Getter
@Setter
public class Rectangle implements QuadRangle{//长方形
    private long height;
    private long width;
}

public interface QuadRangle {//四边形接口
    long getWidth();
    long getHeight();
}

@Setter
@Getter
public class Square implements QuadRangle{//正方形
    private long length;
    @Override
    public long getWidth() {
        return length;
    }
    @Override
    public long getHeight() {
        return length;
    }
}

public class SimpleTest {
    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(10);
        rectangle.setHeight(5);
        resize(rectangle);

        Square square = new Square();
        square.setLength(10);
        resize(square);
    }
}

然后这里的 Rectangle参数就不能被改变了,如果传入QuadRangle则会报错,避免了继承泛滥问题

public static void resize(Rectangle rectangle) {

正方形也无法调用resize方法,不满足条件,这就通过修改符合了里氏替换原则。

resize(square);

这也只是类的继承关系结构之间的里氏替换原则的体现。方法上的里氏替换原则的具体体现就不再演示了。日常中大家也一直在使用。

你可能感兴趣的:(设计模式,java,里氏替换原则)