6大设计原则(2):里氏替换原则
里氏替换原则: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个含义所讲到的,子类的输入参数宽于或等于父类的参数,也就是说这个方法时不会被调用的。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |