本文来了解一下什么是里氏替换原则。
定义
- 如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型T2时类型T1的子类型。
- 一个软件实体如果适用于一个父类的话,那么一定适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。(所以避免子类覆盖父类的方法,而是子类新增方法满足实现)
- 引申意义:子类可以扩展父类的功能,但不能改变父类原有的功能
- 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法
- 子类中可以增加自己特有的方法
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松
- 当子类的方法实现父类的方法时(重写/重载或实现实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类返回值更严格或相等。
例子1
我们都知道,正方形是特殊的长方形,那么正方形是长方形的子类。但是对于某种场景,这种继承关系是不符合里氏替换原则的。我先定义一个长方形类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public class Rectangle { private int width; private int length;
public int getWidth() { return width; }
public void setWidth(int width) { this.width = width; }
public int getLength() { return length; }
public void setLength(int length) { this.length = length; } }
|
再定义一个正方形类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class Square extends Rectangle{ private int sideLength;
public int getSideLength() { return sideLength; }
public void setSideLength(int sideLength) { this.sideLength = sideLength; }
@Override public int getWidth() { return getSideLength(); }
@Override public void setWidth(int width) { this.setLength(width); }
@Override public int getLength() { return getSideLength(); }
@Override public void setLength(int length) { this.setLength(length); } }
|
测试,场景是判断宽度小于等于长度的话,宽度则加一。测试长方形是OK的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Test {
public static void resize(Rectangle rectangle){ while(rectangle.getWidth() <= rectangle.getLength()){ rectangle.setWidth(rectangle.getWidth()+1); System.out.println("宽度为:"+rectangle.getWidth()+",长度为:"+rectangle.getLength()); } }
public static void main(String[] args) { Rectangle rectangle = new Rectangle(); rectangle.setWidth(10); rectangle.setLength(20); resize(rectangle); } }
|
但是测试正方形的时候,就会死循环了,因为一直都满足这个条件。也就是说正方形这个子类替换掉父类的长方形类时,程序出错了,不满足里氏替换原则了。
例子2
子类的方法入参要比父类的宽松。
定义一个基类Base:
1 2 3 4 5
| public class Base { public void hello(HashMap map){ System.out.println("base is running"); } }
|
定义一个子类Child:
1 2 3 4 5 6 7 8 9 10
| public class Child extends Base{ @Override public void hello(HashMap map) { System.out.println("child hashmap is running..."); }
public void hello(Map map){ System.out.println("child map is running..."); } }
|
测试:
1 2 3 4 5 6 7
| public class Test { public static void main(String[] args) { Child child = new Child(); HashMap map = new HashMap(); child.hello(map); } }
|
结果肯定打印: child hashmap is running…
但是如果我将子类重写的方法删除呢?结果是: base is running
如果反过来,父类入参是Map,子类重载的方法入参是HashMap,那么就会执行这个重载的方法。在业务开发中,这样子可能会造成业务逻辑的混乱。