里氏替换原则:LSP
定义:
如果对于每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都换为o2时,程序的行为没有发生变化,那么S是T的子类型。
在继承的时候,父类出现的地方子类就可以出现,子类可替代父类,因为子类中有父类的方法,然而父类却不可以替代子类,因为子类中可能有父类没有的方法。这就是所谓的向下转型是不安全的。
使用继承有很多优点,可以提高代码的重用性,提高可扩展性、开放性,但是不可否认,继承也是有缺点的:
1.继承是侵入性的,只要继承,就必须拥有父类的所有属性和方法;
2.降低代码的灵活性
3.增强了耦合性。
解决方案就是里氏替换原则。
4个含义:
1.子类必须完全实现父类的方法
2.子类可以有自己的方法
3.覆盖或实现父类的方法时,输入参数可以被放大
4.覆写或实现父类的方法时,输出结果可以被缩小
前两个含义比较好理解,这里就不再赘述,主要说一下3和4。
先说第3个,覆盖或实现父类的方法时,输入参数可以被放大。
先看一个例子:
class Father { public Collection dosomething(HashMap map) { System.out.println("父类被执行--->"); return map.values(); } } class Son extends Father { public Collection dosomething(Map map) { System.out.println("子类被执行--->"); return map.values(); } } public class Client { public static void main(String[] args) { // 父类存在的地方就可以替换为子类 Father f = new Father(); HashMap map = new HashMap(); f.dosomething(map); } }
代码运行结果是:
父类被执行--->
根据里氏替换原则,将父类改为子类:
public class Client { public static void main(String[] args) { // 父类存在的地方就可以替换为子类 // Father f = new Father(); Son f = new Son(); HashMap map = new HashMap(); f.dosomething(map); } }
然而输出结果还是父类被执行。。。
父类方法的参数是HashMap,而子类方法的参数是Map,也就是说子类的参数类型范围大,子类代替父类传递到调用者中,子类的方法永远不会被执行。如果想要执行子类中的方法的话就需要覆写父类中的方法,覆写就是父类中的方法一模一样的出现在子类中。
class Father { public Collection dosomething(HashMap map) { System.out.println("父类被执行--->"); return map.values(); } } class Son extends Father { // public void dosomething(Map map) { // System.out.println("子类被执行--->"); // } @Override public Collection dosomething(HashMap map) { // TODO Auto-generated method stub System.out.println("子类被执行--->"); return map.values(); } } public class Client { public static void main(String[] args) { // 父类存在的地方就可以替换为子类 // Father f = new Father(); Son f = new Son(); HashMap map = new HashMap(); f.dosomething(map); } }
这是正常的。
如果父类参数的参数类型范围大于子类输入参数类型的话,会出现什么问题呢?会出现父类存在的地方,子类就未必可以存在,因为一旦把子类作为参数传入,调用者就很可能进入子类的方法范畴。
修改一下上面的代码,扩大父类参数范围,缩小子类参数范围。
class Father { public Collection dosomething(Map map) { System.out.println("父类被执行--->"); return map.values(); } } class Son extends Father { public Collection dosomething(HashMap map) { System.out.println("子类被执行--->"); return map.values(); } } public class Client { public static void main(String[] args) { // 父类存在的地方就可以替换为子类 Father f = new Father(); Son f1 = new Son(); HashMap map = new HashMap(); f.dosomething(map); f1.dosomething(map); } }
f执行父类的方法,f1执行子类的方法。
这就不正常了
子类在没有覆写父类方法的情况下,子类方法被执行了。所以,子类中方法的参数范围(前置条件)必须与父类的参数范围(前置条件)相同或者更加宽松。
再来说一下第4个含义,覆写或实现父类的方法时,输出结果可以被缩小。
什么意思呢?父类方法的返回值是类型T,子类相同方法(重载或覆写)的返回值是S,那么里氏替换原则就要求S必须小于等于T。也就是说,要么S和T类型相同,要么S是T的子类。
对于覆写而言,父类和子类中的方法时一模一样的,所以返回类型也应当是一样的。
对于重载,也就是第3个含义所讲到的,子类的输入参数宽于或等于父类的参数,也就是说这个方法时不会被调用的。
版权声明:本文为博主原创文章,未经博主允许不得转载。