软件构造中Behavioral subtyping and Liskov Substitution Principle

本文深入探讨了面向对象编程中的行为子类型和Liskov替换原则(LSP),解释了子类型多态的概念,并通过实例展示了Java中的规则和限制。LSP强调子类型必须能够替换其基类型而不影响程序的正确性,包括协变、逆变和泛型中的应用。文章还讨论了LSP在异常处理和泛型中的表现,总结了确保子类型兼容性的关键点。
摘要由CSDN通过智能技术生成

目录

一、Behavioral subtyping

二、 Liskov Substitution Principle (LSP)

 2.1 协变

 2.2 逆变

2.3 泛型中的LSP

三、总结


一、Behavioral subtyping

在介绍本次主题之前,首先引入一个概念——子类型多态:客户端可用统一的方式处理不同类型的对象。下面是一个例子。

Animal a = new Animal();
Animal c1 = new Cat();
Cat c2 = new Cat();
在可以使用a的场景,都可以用c1和c2代
替而不会有任何问题
a = c1;
a = c2;

 如果对于类型T的对象x,q(x) 成立,那么对于类型T的子类型S的对象y,q(y) 也成立。

这句话出自于著名图灵奖获得者Barbara Liskov (1939- ) MIT http://www.pmg.csail.mit.edu/~liskov

美国第一位计算机科学方向的女博士 2008年图灵奖获得者 提出了第一个支持数据抽象的面向对象编程语 言CLU,对现代主流语言如C++/Java/Python /Ruby/C#都有深远的影响。她所提炼出来的数据抽象思想,成为软件工程的重要精髓之一。 她提出的“Liskov替换原则”,是面向对象最重要的几大原则(SOLID)之一。

下面给出了关于Java中编译器强制执行的规则(静态类型检查)的一些总结。

  • 子类型可以增加方法,但不可删
  • 子类型需要实现抽象类型 (接口、抽象类)中所有未实现的方法
  • 子类型中重写的方法必须有相同或子类型的返回值或者符合co-variant的参数
  • 子类型中重写的 方法必须使用同样类型的参数或者符合contra-variant的参数(此种情况Java目 前按照重载overload处理)
  • 子类型中重写的方法不能抛出额外的异常 

 下面给出的例子符合以下定义。

  • 子类实现相同的不变量(以及其他不变量)
  • 重写的方法具有相同的前置和后置条件

二、 Liskov Substitution Principle (LSP)

LSP是子类型关系的特定定义,称为强行为子类型化。在编程语言中,LSP依赖于以下限制:

  • 前置条件不能强化
  • 后置条件不能弱化
  • 不变量要保持
  • 子类型方法参数:逆变
  • 子类型方法的返回值:协变
  • 异常类型:协变

 2.1 协变

父类型→子类型:越来越具体specific 返回值类型:不变或变得更具体 异常的类型:也是如此。下面这个例子,就是子类型方法的返回值的协变。

class T {
Object a() { … }
}

class S extends T {
@Override 
String a() { … } 
} 

下面的例子是关于异常类型的协变。 为子类型的方法声明的每个异常都应该是为父类型的方法声明的某个异常的子类型。

class T {
void b( ) throws Throwable {…}
}

class S extends T {
@Override 
void b( ) throws IOException {…}
}

class U extends S {
@Override 
void b( ) {…}
} 

 2.2 逆变

父类型→子类型:越来越具体specific 参数类型:要相反的变化,要不变或越来 越抽象。不过目前Java中遇到这种情况,当作overload看待。

class T {
void c( String s ) { … }
}

class S extends T {
@Override 
void c( Object s ) { … } 
} 

2.3 泛型中的LSP

先看一个例子。

List<Integer> myInts = new ArrayList<>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; 
myNums.add(3.14); 
//compiler error
static long sum(List<Number> numbers) {
long summation = 0;
for(Number number : numbers) {
summation += number.longValue();
}
return summation;
}
List<Integer> myInts = Arrays.asList(1,2,3,4,5);
List<Long> myLongs = Arrays.asList(1L, 2L, 3L, 4L, 5L);
List<Double> myDoubles = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0);
sum(myInts); 
sum(myLongs);
sum(myDoubles);
//compiler error
//compiler error
//compiler error

 对于以上例子的原理解释。

  •  虚拟机中没有泛型类型对象-所有对象都属于普通类!
  • 泛 型信息只存在于编译阶段,在运行时会被”擦除”
  • 定义泛型类型时,会自动提 供一个对应的原始类型(非泛型类型),原始类型的名字就是去掉类型参数后的 泛型类型名。
  • 擦除时类型变量会被擦除,替换为限定类型, 如果没有限定类型则替换为Object类型。

三、总结

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值