里式替换原则(Liskov Substitution Principle)
引言
里式替换原则是用来约束继承的使用的一种规范。里式替换原则鼓励开发者使用接口或抽象类开发,用接口或抽象类的引用来调用对象。
继承的优点
代码共享,减少创建类的工作量。每个子类拥有父类的方法和属性。
提高代码的重用性。
子类完全继承父类,但也可以拥有自己的方法和属性,是对父类的扩展。
提高代码的可扩展性,扩展接口可以继承父类。
提高产品或项目的开放性。
继承的缺点
继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法。
降低代码的灵活性。子类必须拥有父类的属性和方法。
增强了耦合性。当父类被修改,子类也必须要进行响应的更改,这会导致代码的大量重构。
为了避免继承的缺点,引入里式替换原则
定义
第一种定义
If for each object o1 of type S there is an object o2 of type T such that for all progeams P defined in terms of T ,the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T .
如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有对象o1都代换为o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。
理解:类型T的对象能够替换类型S的对象,也就是说类型T包含类型S(T是S的父类)
第二种定义
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it
所有引用基类的地方必须能透明地使用其子类的对象。
理解:只要父类能出现的地方,子类就可以出现,而且替换子类也不会产生任何错误和异常,使用者根本不用知道是父类还是子类。但是有子类的地方,父类未必能适应。也就是说,父类对象和子类对象没有太大区别,使得使用者能够使用父类接口来使用子类对象。
含义
子类必须完全实现父类的抽象方法,不能重写父类已实现的方法应用
子类必须完全实现父类的抽象方法: 因为java中子类需要完整的实现父类的抽象方法,如果子类不完全实现父类抽象方法,那么这个子类也是一个抽象类,因为实体类必须完全实现抽象类的方法。在这里说的子类一般指代实体类,因此实体类必须完全实现抽象父类的抽象方法。
子类不能重写父类已实现的方法: 父类已实现的方法是一种已定好的契约和规范。
子类可以有自己的个性应用
子类继承了父类,拥有父类的方法,子类也能有自己的方法和属性。
如果子类转为父类类型很安全(向上转型,子类完全兼容父类),但父类类型转子类类型则不安全(向下转型,子类有父类没有的东西,父类不能完全兼容子类)。
覆盖或实现父类的方法时输入(输入参数被称为前置条件 )可以被放大 应用
子类重载父类方法时,当父类的参数类型范围比子类参数类型范围大时,子类会调用子类自己重载父类的方法,却不会调用子类继承父类的方法。这样子类并不能代替父类执行,不符合里式替换法则。
子类重载父类方法时,当父类参数类型范围小于子类参数类型范围时,子类对代替父类执行父类的方法。
因此,子类的参数范围需要大于或等于父类的参数类型范围,这样,会调用自动匹配子类继承父类的方法,不会调用子类重载的方法,从而使程序逻辑混乱。
覆写或实现父类的方法时输出结果(返回结果被称为后置条件 )可以被缩小应用
父类一个方法返回值是类型T,子类重写或重载的方法返回值是S,那么T必须是S的父类(T大于等于S)
应用
应用场景1
子类必须完全实现父类的抽象方法
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 public abstract class MilitaryAircraft { public void fly () { System.out.println("我能飞" ); } public abstract String type () ; public abstract void attack () ; public abstract void intro () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class J20 extends MilitaryAircraft { @Override public String type () { return "第四代隐身重型战斗机J20" ; } @Override public void attack () { System.out.println("战斗机攻击" ); } @Override public void intro () { System.out.println("我是解放军序列第四代隐身重型战斗机J20" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class Pilot { private MilitaryAircraft militaryAircraft; public Pilot () { } public Pilot (MilitaryAircraft militaryAircraft) { this .militaryAircraft = militaryAircraft; } public MilitaryAircraft getMilitaryAircraft () { return militaryAircraft; } public void setMilitaryAircraft (MilitaryAircraft militaryAircraft) { this .militaryAircraft = militaryAircraft; } public void fighting () { System.out.println("飞行员驾驶" +militaryAircraft.type()+"开始战斗" ); militaryAircraft.attack(); } }
1 2 3 4 5 6 7 8 9 10 public class Test { public static void main (String[] args) { Pilot pilot = new Pilot(); pilot.setMilitaryAircraft(new J20()); pilot.fighting(); } }
1 2 飞行员驾驶第四代隐身重型战斗机J20开始战斗 战斗机攻击
如果子类没有完全实现父类的抽象方法(举例可能有失偏颇)
再创建子类(运输机不能攻击,如果这个子类直接继承父类,会出现问题)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class C130 extends MilitaryAircraft { @Override public String type () { return "军用运输机C130" ; } @Override public void attack () { } @Override public void intro () { System.out.println("我是美军序列军用运输机大力神C130" ); } }
测试
C130是运输机,是执行运输任务的飞机,并不是战斗单位,因此,不能继承父类的attack方法。
1 2 3 4 5 6 7 8 9 10 public class Test { public static void main (String[] args) { Pilot pilot = new Pilot(); pilot.setMilitaryAircraft(new C130()); pilot.fighting(); } }
测试结果
上面的虚构一个攻击方法使得运输机也上了战场,但是没有战斗力,那只有被击落的份。
为了解决这个问题,我们必须将运输机这个子类与父类断开继承关系,再创建一个运输机父类,让C130继承这个。这使得C130运输机脱离战斗序列
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 public abstract class Transport { private MilitaryAircraft militaryAircraft ; public Transport () { } public Transport (MilitaryAircraft militaryAircraft) { this .militaryAircraft = militaryAircraft; } public void fly () { militaryAircraft.fly(); } public abstract String type () ; public abstract void intro () ; public abstract void transport () ; }
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class Pilot { private MilitaryAircraft militaryAircraft; private Transport transport ; public Pilot () { } public Pilot (MilitaryAircraft militaryAircraft) { this .militaryAircraft = militaryAircraft; } public Pilot (MilitaryAircraft militaryAircraft, Transport transport) { this .militaryAircraft = militaryAircraft; this .transport = transport; } public MilitaryAircraft getMilitaryAircraft () { return militaryAircraft; } public void setMilitaryAircraft (MilitaryAircraft militaryAircraft) { this .militaryAircraft = militaryAircraft; } public Transport getTransport () { return transport; } public void setTransport (Transport transport) { this .transport = transport; } public void transport () { System.out.println("飞行员驾驶" +transport.type()); transport.transport(); } public void fighting () { System.out.println("飞行员驾驶" +militaryAircraft.type()+"开始战斗" ); militaryAircraft.attack(); } }
1 2 3 4 5 6 7 8 9 public class Test { public static void main (String[] args) { Pilot pilot = new Pilot(); pilot.setTransport(new C130()); pilot.transport(); } }
子类不能重写父类已实现的方法
1 2 3 4 5 6 7 8 public class Parent { public int add (int a , int b) { return a+b ; } }
1 2 3 4 5 6 7 8 public class Child extends Parent { public int add (int a , int b) { return a+(-b) ; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Test { public static void main (String[] args) { Parent parent = new Parent(); System.out.println("执行父类" ); int add = parent.add(1 , 4 ); System.out.println("父类执行结果:" +add); Parent child = new Child(); System.out.println("执行子类" ); int add1 = child.add(1 , 4 ); System.out.println("之类执行结果:" +add1); } }
测试结果
父类执行的结果与子类代替父类执行的结果不同,这违反了里式替换原则
1 2 3 4 执行父类 父类执行结果:5 执行子类 之类执行结果:-3
应用场景2
子类可以有自己的个性
1 2 3 4 5 6 7 8 9 public class Parrent { public int max (int a , int b) { if (a == b){ System.out.println(a+"==" +b); return 0 ; } return a>b?a:b; } }
1 2 3 4 5 public class Child extends Parrent { public void intro () { System.out.println("我是子类,我为我自己代言!" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Test { public static void main (String[] args) { System.out.println("父类开始执行" ); Parrent parrent = new Parrent(); int max = parrent.max(3 , 4 ); System.out.println("最大值为:" +max); System.out.println("子类开始执行" ); Child child = new Child(); child.intro(); } }
测试结果
子类继承父类,但子类也可以有自己的方法,也就是个性。如果用父类的引用指向子类对象,使用的是父类方法,不能使用子类方法。如果子类引用指向子类对象,能调用子类方法和父类方法。
1 2 3 4 父类开始执行 最大值为:4 子类开始执行 我是子类,我为我自己代言!
应用场景3
覆盖或实现父类的方法时输入(输入参数被称为前置条件 )可以被放大
1 2 3 4 5 6 7 import java.util.HashMap;public class Parrent { public void function (HashMap map) { System.out.println("父类方法被执行" ); } }
1 2 3 4 5 6 7 import java.util.Map;public class Child extends Parrent { public void function (Map map) { System.out.println("子类被执行" ); } }
1 2 3 4 5 6 7 8 9 10 11 import java.util.HashMap;public class Test { public static void main (String[] args) { HashMap<Object, Object> map = new HashMap<>(); Parrent parrent = new Parrent(); Child child = new Child(); parrent.function(map); child.function(map); } }
反例
1 2 3 4 5 6 7 import java.util.Map;public class Parrent { public void function (Map map) { System.out.println("父类方法被执行" ); } }
1 2 3 4 5 6 7 import java.util.HashMap;public class Child extends Parrent { public void function (HashMap map) { System.out.println("子类被执行" ); } }
1 2 3 4 5 6 7 8 9 10 11 import java.util.HashMap;public class Test { public static void main (String[] args) { HashMap<Object, Object> map = new HashMap<>(); Parrent parrent = new Parrent(); Child child = new Child(); parrent.function(map); child.function(map); } }
应用场景4
覆写或实现父类的方法时输出结果(返回结果被称为后置条件 )可以被缩小
1 2 3 4 5 import java.util.Map;public abstract class Parrent { public abstract Map function () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import java.util.HashMap;public class Child extends Parrent { @Override public HashMap<Integer,String> function () { HashMap<Integer, String> map = new HashMap<>(); map.put(1 ,"Linux" ); map.put(2 ,"Windows" ); map.put(3 ,"mac" ); return map; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.util.HashMap;import java.util.Map;import java.util.Set;public class Test { public static void main (String[] args) { Child child = new Child(); HashMap<Integer,String> map = child.function(); Set<Map.Entry<Integer, String>> entrySet = map.entrySet(); for (Map.Entry<Integer,String> entry : entrySet){ Integer key = entry.getKey(); String value = entry.getValue(); System.out.println(key+"-----" +value); } } }
1 2 3 1 -----Linux2 -----Windows3 -----mac