定义:如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有对象o1替换为o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
里式替换原则是继承复用的基石,是实现抽象化的规范,只有子类可以替换父类,并且功能不受影响时,父类才能真正的被复用,而子类也能在父类的基础上增加新的功能,里式替换原则是对开闭原则的一个补充。
定义扩展:如果一个软件实体适用一个父类的话,那一定适用于其子类,所有引用父类的地方必须能透明的使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。
引申含义:子类可以扩展父类的功能,但不能改变父类原有的功能。
含义1:子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
含义2:子类可以增加自己特有的方法。
含义3:当子类的方法重载父类的方法时,方法的前置条件(输入/入参),要比父类方法的输入参数更加宽松。
含义4:当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(输出/返回值)要比父类更加严格或相等。
优点:约束继承泛滥,开闭原则的一种体现。
案例
此处我们以经典的长方形和正方形为例,假设正方形是一种特殊的长方形,我们模拟长方形的长,不断的减少,直至与宽相等,此时长方形变成了一个正方形,程序退出运行,V1版本的代码如下,
public class Rectangle { private long length; private long width; public long getWidth() { return width; } public long getLength() { return length; } public void setLength(long length) { this.length = length; } public void setWidth(long width) { this.width = width; } }
public class Square extends Rectangle { private long sideWidth; public long getSideWidth() { return sideWidth; } public void setSideWidth(long sideWidth) { this.sideWidth = sideWidth; } @Override public long getWidth() { return getSideWidth(); } @Override public long getLength() { return getSideWidth(); } @Override public void setLength(long length) { setSideWidth(length); } @Override public void setWidth(long width) { setSideWidth(width); } }
public class Test { public static void resize(Rectangle rectangle){ while (rectangle.getWidth() <= rectangle.getLength()){ rectangle.setWidth(rectangle.getWidth()+1); System.out.println("width:"+rectangle.getWidth() + " length:"+rectangle.getLength()); } System.out.println("resize方法结束 width:"+rectangle.getWidth() + " length:"+rectangle.getLength()); }// public static void main(String[] args) { // Rectangle rectangle = new Rectangle(); // rectangle.setWidth(10); // rectangle.setLength(20); // resize(rectangle); // } public static void main(String[] args) { Square square = new Square(); square.setLength(10); resize(square); } }
当我们在Test类中的main方法使用长方形作为入参的时,程序运行正常,但是当使用正方形作为入参进行实验时,程序出现了死循环,因为正方形的长宽相等,while (rectangle.getWidth() <= rectangle.getLength()),一直满足这个循环,而rectangle.setWidth(rectangle.getWidth()+1)又同时将长宽进行了赋值,所以出现了堆栈溢出的情况。
所以上述案例中,违反了里式替换原则的设计。所以要打破长方形和正方形的依赖关系,可以在上层在抽象出一个四边形类,让长方形和正方形都实现顶层的抽象,V2的代码如下。
四边形接口
public interface Quadrangle { long getWidth(); long getLength(); }
public class Rectangle implements Quadrangle { private long length; private long width; @Override public long getWidth() { return width; } @Override public long getLength() { return length; } public void setLength(long length) { this.length = length; } public void setWidth(long width) { this.width = width; } }
public class Square implements Quadrangle { private long sideLength; public long getSideLength() { return sideLength; } public void setSideLength(long sideLength) { this.sideLength = sideLength; } @Override public long getWidth() { return sideLength; } @Override public long getLength() { return sideLength; } }
public class Test { public static void resize(Rectangle rectangle){ while (rectangle.getWidth() <= rectangle.getLength()){ rectangle.setWidth(rectangle.getWidth()+1); System.out.println("width:"+rectangle.getWidth() + " length:"+rectangle.getLength()); } System.out.println("resize方法结束 width:"+rectangle.getWidth() + " length:"+rectangle.getLength()); } public static void main(String[] args) { Rectangle rectangle = new Rectangle(); rectangle.setWidth(10); rectangle.setLength(20); resize(rectangle); } // public static void main(String[] args) { // Square square = new Square(); // square.setLength(10); // resize(square); // } }
此时的resize方法的入参我们就进行了约束,只能传入Rectangle,而不能进行替换成Square,从而满足了业务的要求,约束了继承的泛滥。
补充
入参的约束
public class Base { public void method(HashMap map){ System.out.println("父类被执行"); } }
public class Child extends Base { /** * 重写 * @param map */ @Override public void method(HashMap map) { System.out.println("子类HashMap入参方法被执行"); } /** * 重载 * @param map */ public void method(Map map) { System.out.println("子类HashMap入参方法被执行"); } }
public class Test { public static void main(String[] args) { Base child = new Child(); HashMap hashMap = new HashMap(); child.method(hashMap); } }
执行的结果:子类HashMap入参方法被执行
假如将Child代码进行调整
public class Child extends Base { /** * 重写 * @param map */ // @Override // public void method(HashMap map) { // System.out.println("子类HashMap入参方法被执行"); // } /** * 重载 * @param map */ public void method(Map map) { System.out.println("子类HashMap入参方法被执行"); } }
再次运行程序,结果:父类被执行
对代码再次进行调整
public class Base { public void method(Map map){ System.out.println("父类被执行"); } }
*/ public class Child extends Base { /** * 重写 * @param map */ @Override public void method(Map map) { System.out.println("子类Map入参方法被执行"); } /** * 重载 * @param map */ public void method(HashMap map) { System.out.println("子类HashMap入参方法被执行"); } }
public class Test { public static void main(String[] args) { Base child = new Child(); HashMap hashMap = new HashMap(); child.method(hashMap); } }
程序运行结果: 子类HashMap入参方法被执行,这样的话就违反了里式替换替换原则,所以子类的入参要比父类更加宽松。
返回值的约束
public abstract class Base { public abstract Map method(); }
public class Child extends Base { @Override public HashMap method() { HashMap hashMap = new HashMap(); System.out.println("子类method被执行"); hashMap.put("message","子类method被执行"); return hashMap; } }
public class Test { public static void main(String[] args) { Child child = new Child(); System.out.println(child.method()); } }
子类的返回值可以是Map也可以是更加严格的HashMap,但是不能比Map更宽松,否则会出现编译错误。