4.2 面向复用的软件构造技术
1. 设计可重用的类
在OOP中设计可重用的类
▪封装和信息隐藏
▪继承与超越
▪多态、子类型和重载
▪泛型编程
▪行为分型和利斯科夫替代原理(LSP)
▪授权和组成
(1)行为分型和利斯科夫替代原理(LSP)
行为分型
子类型多态:客户端可用统一的方式处理
不同类型的对象
如果对于类型T的对象x,q(x) 成立,那么对于类型T的子类型S的对象y,q(y) 也成立。
Java中编译器强制的规则(静态类型检查)
子类型可以增加方法,但不可删
子类型需要实现抽象类型 (接口、抽象类)中所有未实现的方法
子类型中重写的方法必须有相同或子类型的返回值或者符合co-variant的参数
子类型中重写的
方法必须使用同样类型的参数或者符contra-variant的参数(此种情况Java目前按照重载overload处理)
子类型中重写的方法不能抛出额外的异常
也适用于指定的行为(方法):
更强的不变量
更弱的前置条件
更强的后置条件
利斯科夫替代原理(LSP)
LSP是子类型关系的一个特殊定义,称为
强行为子类型化
在编程语言中,LSP依赖于以下限制:
前置条件不能强化
后置条件不能弱化
不变量要保持
子类型方法参数:逆变
子类型方法的返回值:协变
异常类型:协变
协变
父类型→子类型:越来越具体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( ) {
…}
}
反协变、逆变
父类型→子类型:越来越具体specific
参数类型:要相反的变化,要不变或越来
越抽象
class T {
void c( String s ) {
… }
}
class S extends T {
@Override
void c( Object s ) {
… }
}
目前Java中遇到这种情况,当作overload看待
协变和逆变
Java中数组是协变的: 对T[]数组,可以保存类型T及其子类型的数据
泛型中的LSP
泛型是类型不变的
ArrayList是List的子类型
List不是List的子类型
类型参数在编译后被丢弃,在运行时不可用
这个过程称为类型擦除
泛型不是协变的。
可采用通配符实现两个泛型类的协变
** Class类型类**
Java在运行时,为所有对象维护一个运行时类型标识,这个标识跟踪对象所属的类,用来确定选择哪个方法运行。保存这些信息的类叫做“Class类型类”。注意:Class是类的名字,不是关键词class。每个”Class”的对象描述了一个类的信息
什么是类型擦除?
虚拟机中没有泛型类型对象-所有对象都属于普通类
泛型信息只存在于编译阶段,在运行时会被”擦除”
定义泛型类型时,会自动提供一个对应的原始类型(非泛型类型),原始类型的名字就是去掉类型参数后的泛型类型名。
擦除时类型变量会被擦除,替换为限定类型,如果没有限定类型则替换为Object类型。
运行时类型查询只适用于原始类型
泛型的通配符
无界通配符类型是使用通配符(?)指定的,例如List<?>。
有两种情况下,无界通配符是一种有用的方法:
-如果你正在写一个可以使用Object类中提供的功能来实现的方法。
-当代码使用泛型类中不依赖于类型参数的方法时。例如,列表。大小或List.clear。
-事实上,Class<?>之所以经常使用,是因为其中的大多数方法
类不依赖于T。
<? super A> 下限通配符
<? extends A> 上限通配符
(2)授权和组成
Comparator接口
这个接口对实现它的每个类的对象进行总体排序。
▪这种排序被称为类的自然排序,类的比较方法被称为类的自然比较方法。
另一种方法:让你的ADT实现Comparable接口,然后override compareTo() 方法
与使用Comparator的区别:不需要构建新的Comparator类,比较代
码放在ADT内部。
这不再是委托
委托
委派/委托:一个对象请求另一个对象的功能
委派是复用的一种常见形式
Sorter可以使用任意排序顺序重用
Comparators可以在需要比较整数的任意客户端代码中重用
可以将委托描述为在实体之间共享代码和数据的低级机制。
-显式委托:将发送对象传递给接收对象
-隐式委托:通过语言的成员查找规则
委托与继承
继承:通过新操作扩展基类或覆盖操作。
委托:捕捉一个操作并将其发送到另一个对象
很多设计模式将继承和委托结合使用
用委托代替继承
如果子类只需要复用父类中的一小部分方法
可以不需要使用继承,而是通过委派机制