设计模式6大原则:里氏置换原则

里氏置换原则(Liskov Substitution Principle),简称LSP

定义:

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

所有引用基类的地方必须能够透明的使用其子类对象。

也就是说,只要父类出现的地方子类就能够出现,而且替换为子类不会产生任何错误或异常。但是反过来,子类出现的地方,替换为父类就可能出现问题了。

这个原则是为良好的继承定义一个规范,简单的讲,有4层含义:

一、子类必须完全实现父类的方法

定义一个抽象类

  1. public abstract class ViewPoint {
  2.     //去丽江旅游
  3.     public abstract void where();
  4. }

下面两个类是实现这个抽象类

  1. public class Lijiang extends ViewPoint {
  2.     @Override
  3.     public void where() {
  4.         System.out.println(“欢迎来到丽江…”);
  5.     }
  6. }
  7. public class Zhangjiajie extends ViewPoint {
  8.     @Override
  9.     public void where() {
  10.         System.out.println(“欢迎来到张家界…”);
  11.     }
  12. }

人物是涂涂,在里面设置类类型来传递参数。此时涂涂要去的旅游景点还是抽象的

  1. public class Tutu {
  2.     //定义要旅游的景点
  3.     private ViewPoint viewpoint;
  4.     //涂涂要去的景点
  5.     public void setViewPoint(ViewPoint viewpoint)
  6.     {
  7.         this.viewpoint = viewpoint;
  8.     }
  9.     public void travelTo()
  10.     {
  11.         System.out.println(“涂涂要去旅游了”);
  12.         viewpoint.where();
  13.     }
  14. }

场景类。设置具体要去的景点

  1. public class Sence {
  2.     public static void main(String args[])
  3.     {
  4.         Tutu tutu = new Tutu();
  5.         //设置要去的旅游景点
  6.         tutu.setViewPoint(new Lijiang());
  7.         tutu.travelTo();
  8.     }
  9. }

运行结果:

涂涂要去旅游了
欢迎来到丽江…

二、子类可以有自己的特性

也就是说在类的子类上,可以定义其他的方法或属性

三、覆盖或者实现父类的方法时输入参数可以被放大

父类能够存在的地方,子类就能存在,并且不会对运行结果有变动。反之则不行。

父类,say()里面的参数是HashMap类型,是Map类型的子类型。(因为子类的范围应该比父类大)

  1. import java.util.Collection;
  2. import java.util.HashMap;
  3. public class Father {
  4.     public Collection say(HashMap map)
  5.     {
  6.         System.out.println(“父类被执行…”);
  7.         return map.values();
  8.     }
  9. }

子类,say()里面的参数变成了Map类型,Map范围比HashMap类型大,符合LSP原则。注意这里的say不是覆写父类的say,因为参数类型不同。而是重载。

  1. import java.util.Collection;
  2. import java.util.Map;
  3. /*
  4.  * 子类继承了父类的所有属性
  5.  */
  6. public class Son extends Father {
  7.     //方法输入参数类型
  8.     public Collection say(Map map)
  9.     {
  10.         System.out.println(“子类被执行…”);
  11.         return map.values();
  12.     }
  13. }

场景类

  1. import java.util.HashMap;
  2. public class Home {
  3.     public static void main(String args[])
  4.     {
  5.         invoke();
  6.     }
  7.     public static void invoke()
  8.     {
  9.         //父类存在的地方,子类就应该能够存在
  10.         //Father f = new Father();
  11.         Son s = new Son();
  12.         HashMap map = new HashMap();
  13.         //f.say(map);
  14.         s.say(map);
  15.     }
  16. }

无论是用父类还是子类调用say方法,得到的结果都是

父类被执行…

但是,如果将上面Father里的say参数改为Map,子类Son里的say参数改为HashMap,得到的结果就变成了

f.say(map)结果:父类被执行…

s.say(map)结果: 子类被执行…

这样会造成逻辑混乱。所以子类中方法的前置条件必须与父类中被覆写的前置条件相同或者更宽。

四、覆写或者实现父类的方法时输出结果可以被缩小

其实与上面的类似,也就是父类能出现的地方子类就可以出现,而且替换为子类不会产生任何错误或者异常,使用者也无需知道是父类还是子类。但是反过来就不行了,有子类出现的地方,父类未必就适应。(毕竟子类的范围要>=父类的范围)