目录
  1. 1. 里式替换原则(Liskov Substitution Principle)
    1. 1.1. 引言
    2. 1.2. 定义
      1. 1.2.1. 第一种定义
      2. 1.2.2. 第二种定义
      3. 1.2.3. 含义
    3. 1.3. 应用
      1. 1.3.1. 应用场景1
        1. 1.3.1.1. 子类必须完全实现父类的抽象方法
        2. 1.3.1.2. 如果子类没有完全实现父类的抽象方法(举例可能有失偏颇)
        3. 1.3.1.3. 子类不能重写父类已实现的方法
      2. 1.3.2. 应用场景2
        1. 1.3.2.1. 子类可以有自己的个性
      3. 1.3.3. 应用场景3
        1. 1.3.3.1. 覆盖或实现父类的方法时输入(输入参数被称为前置条件)可以被放大
        2. 1.3.3.2. 反例
      4. 1.3.4. 应用场景4
        1. 1.3.4.1. 覆写或实现父类的方法时输出结果(返回结果被称为后置条件)可以被缩小
里氏代换原则

里式替换原则(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
所有引用基类的地方必须能透明地使用其子类的对象。
理解:只要父类能出现的地方,子类就可以出现,而且替换子类也不会产生任何错误和异常,使用者根本不用知道是父类还是子类。但是有子类的地方,父类未必能适应。也就是说,父类对象和子类对象没有太大区别,使得使用者能够使用父类接口来使用子类对象。

含义
  1. 子类必须完全实现父类的抽象方法,不能重写父类已实现的方法应用
    子类必须完全实现父类的抽象方法: 因为java中子类需要完整的实现父类的抽象方法,如果子类不完全实现父类抽象方法,那么这个子类也是一个抽象类,因为实体类必须完全实现抽象类的方法。在这里说的子类一般指代实体类,因此实体类必须完全实现抽象父类的抽象方法。
    子类不能重写父类已实现的方法: 父类已实现的方法是一种已定好的契约和规范。
  2. 子类可以有自己的个性应用
    子类继承了父类,拥有父类的方法,子类也能有自己的方法和属性。
    如果子类转为父类类型很安全(向上转型,子类完全兼容父类),但父类类型转子类类型则不安全(向下转型,子类有父类没有的东西,父类不能完全兼容子类)。
  3. 覆盖或实现父类的方法时输入(输入参数被称为前置条件)可以被放大 应用
    子类重载父类方法时,当父类的参数类型范围比子类参数类型范围大时,子类会调用子类自己重载父类的方法,却不会调用子类继承父类的方法。这样子类并不能代替父类执行,不符合里式替换法则。
    子类重载父类方法时,当父类参数类型范围小于子类参数类型范围时,子类对代替父类执行父类的方法。
    因此,子类的参数范围需要大于或等于父类的参数类型范围,这样,会调用自动匹配子类继承父类的方法,不会调用子类重载的方法,从而使程序逻辑混乱。
  4. 覆写或实现父类的方法时输出结果(返回结果被称为后置条件)可以被缩小应用
    父类一个方法返回值是类型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
/**
* 应用场景
* 飞机,圆了人类的飞天梦。飞机在20世纪初被发明。其也从最初的种类单一到现在的不同种类的多型机种
*/
public abstract class MilitaryAircraft {
/**
* 飞机能够飞行
*/
public void fly(){
System.out.println("我能飞");
}

/**
* 飞机型号
*/
public abstract String type();

/**
* 飞机动作
*/
public abstract void attack();

/**
* 类介绍
*/
public abstract void intro();

}

  • 创建子类J20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* J20第四代隐身重型战斗机
*/
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
/**
* C130大力神运输机
*/
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();
}
}
  • 测试结果
    上面的虚构一个攻击方法使得运输机也上了战场,但是没有战斗力,那只有被击落的份。
1
飞行员驾驶军用运输机C130开始战斗
  • 为了解决这个问题,我们必须将运输机这个子类与父类断开继承关系,再创建一个运输机父类,让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;
}

/**
* 将飞机的主要功能委托给militaryAircraft处理,transport只需要处理运输机特有的就行了
*/

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
飞行员驾驶军用运输机C130
运输战略物资
子类不能重写父类已实现的方法
  • 创建抽象父类
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) {
/**
* 创建父类对象,调用父类的max方法
*/
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
父类方法被执行
父类方法被执行
反例
  • 创建父类
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);
}
}
  • 测试结果
1
2
父类方法被执行
子类被执行

应用场景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{
/**
* 父类返回值类型可以比子类返回值类型范围大,这也是重写
* 父类返回值比子类返回值小,那就是重载了
* @return
*/
@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-----Linux
2-----Windows
3-----mac
文章作者: rack-leen
文章链接: http://yoursite.com/2019/05/19/Java/%E8%AE%BE%E8%AE%A1%E5%8E%9F%E5%88%99/%E9%87%8C%E6%B0%8F%E4%BB%A3%E6%8D%A2%E5%8E%9F%E5%88%99/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 rack-leen's blog
打赏
  • 微信
  • 支付宝

评论