目录
1. Programing for/with reuse(7.1)
8. Comparator和Comparable(7.5.2)
第9讲考点如下:
一、面向复用的软件构造技术
1. Programing for/with reuse(7.1)
面向复用编程(programming for reuse):开发出可复用的软件
基于复用编程(programming with reuse):利用已有的可复用软件搭建应用系统
复用的意义: 1. 降低成本和开发时间 2. 经过充分 测试,可靠、稳定 3. 标准化,在不同应用中保持一致
可复用代价:
1. 可复用部分应该以一种明确定义的、开放的方式进行设计和构建,具有简明的接口规范、可理解的文档,并着眼于将来的使用。做到这些,需要代价。
2. 可复用成本高昂:它涉及组织、技术和流程的变更,以及支持这些变更的工具的成本,以及培训人员使用新工具和变更的成本。不仅重用程序代价高,可重用程序代价也高。
开发可复用的软件(programming for reuse):
1. 开发成本高于一般软件的成本:要有足够高的适应性
2. 性能差些: 针对更普适场景,缺少足够的针对性
使用已有软件进行开发(programming with reuse):
1. 可复用软件库,对其进行有效的管理
2. 往往无法拿来就用,需要适配
可复用性的衡量:
1. 复用的机会有多频繁?复用的场合有多少?
2. 复用的代价有多大? 搜索、获取;适配、扩展;实例化;与软件其他部分的互连的难度
复用方式:
1.源代码级别的复用
2.模块级别的复用:类/抽象类/接口
3.库级别的复用:API/包
4.系统级别的复用:框架
2. LSP(7.5.1)
子类型多态:客户端可用统一的方式处理不同类型的对象
java中的静态类型检查:
1. 子类型可以增加方法,但不可删
2. 子类型需要实现抽象类型中的所有未实现方法
3. 子类型中重写的方法必须有相同或子类型的返回值或者符合co-variance的参数
4. 子类型中重写的方法必须使用同样类型的参数或者符合contra-variance的参数
5. 子类型中重写的方法不能抛出额外的异常
对于特定的方法,要求:
1. 更强的不变量
2. 更弱的前置条件
3. 更强的后置条件
LSP是子类型关系的一个特殊定义,称为强行为子类型化。
需满足以下原则:
3. 协变、反协变(7.5.1)
(1)协变:
父类型 -> 子类型:越来越具体specific
返回值类型:不变或变得更具体
异常的类型:不变或变得更具体
(2)反协变(逆变)
父类型 -> 子类型:越来越具体specific
参数类型:要相反的变化,要不变或越来越抽象
java不允许出现。出现以重载看待。
4. 数组的子类型化(7.5.1)
数组是协变的:给定Java的子类型规则,T[]类型的数组可以包含T类型的元素或T的任何子类型。
Number[] numbers = new Number[2];
numbers[0] = new Integer(10);
numbers[1] = new Double(3.14);
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
myNumber[0] = 3.14; //run-time error!
在运行时Java知道这个数组实际上被实例化为一个整数数组,而这个数组恰好是通过Number[]类型的引用访问的。
5. 泛型的子类型化(7.5.1)
泛型是类型不变的。编译代码完成后,编译器丢弃类型参数的类型信息;因此,此类型信息在运行时不可用。这个过程称为类型擦除。
泛型不是协变的。
类型擦除:如果泛型类型中的类型参数是无界的,则用它们的边界或对象替换所有类型参数。因此,生成的字节码只包含普通类、接口和方法。
注意: 1. 我们不能把一个整数列表看作是一个数字列表的子类型。这对于类型系统来说是不安全的,编译器会立即拒绝它。
Box<Integer>不是Box<Number>的子类型,即使Integer是Number的子类型。
2. 给定两个具体类型A和B(例如,Number和Integer),MyClass<A>与MyClass<B>没有关系,无论A和B是否相关。MyClass<A>和MyClass<B>的共同父级是Object。
3. 有关在类型参数相关时如何在两个泛型类之间创建类似子类型的关系的信息,请参见通配符。
6. 泛型中的通配符(?)(7.5.1)
无界通配符类型是使用通配符(?)指定的,例如List<?>,这称为未知类型的列表。
有两种情况下,无界通配符是一种有用的方法:
–编写一个可以使用Object类中提供的功能实现的方法。
–当代码在泛型类中使用不依赖于类型参数的方法时。例如,List.size或List.clear。
–事实上,Class<?>经常被使用,因为Class<T>中的大多数方法都不依赖于T。
例:使用printList输出泛型,原来只适用于Object类型。
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
下界通配符: <? super A>
– List<Integer> List<? super Integer>
前者只匹配Integer类型的列表,而后者匹配Integer、Number和Object等Integer超类型的任何类型的列表。
上界通配符:<? extends A>
– List<? extends Number>
7. Delegation(7.5.2)
委派/委托(Delegation):一个对象请求另一个对象的功能,是复用的一种常见形式。
委托可以被描述为在实体之间共享代码和数据的一种低级机制。
–显式委托:将发送对象传递给接收对象
–隐式委托:根据语言的成员查找规则
继承:通过新操作扩展基类或覆盖操作。
委派:捕捉一个操作并将其发送到另一个对象。
如果子类只需要复用父类中的一小部分方法,可以不需要使用继承,而是通过委派机制来实现,通过委托机制调用部分方法,从而避免大量无用的方法。
delegation的种类
1. Use(A use one or multiple B)
2. Association (A has one or multiple B)
3. Composition/aggregation (A owns one or multiple B) ,可以认为Composition/Aggregation是Association的两种具体形
(1)Dependency: 临时性的delegation
使用类的最简单形式是调用它的方法;两个类之间的这种形式的关系称为“uses-a”关系,在这种关系中,一个类使用另一个类,而实际上没有将它作为属性合并-例如,它可以是参数,也可以在方法中局部使用。依赖关系:一个对象的实现需要其他对象(提供者)的临时关系。
(2)Association: 永久性的delegation
关联:对象类之间的持久关系,允许一个对象实例使另一个对象实例代表它执行操作。has_a:一个类将另一个类作为属性/实例变量。这种关系是结构化的,因为它指定一种对象与另一种对象相连接,而不表示行为。
(3)Composition: 更强的association,但难以变化
组合是一种将简单对象或数据类型组合成更复杂对象或数据类型的方法。is_part_of:一个类将另一个类作为属性/实例变量。实现为一个对象包含另一个对象。
(4)Aggregation: 更弱的association,可动态变化
聚合:对象存在于另一个外部,在外部创建,因此它作为参数传递给构造函数。
8. Comparator和Comparable(7.5.2)
接口 Comparator<T>
int compare(T o1, T o2):比较它的两个参数的顺序。
一个比较函数,它对一些对象集合施加总排序。可以将比较器传递给排序方法(如Collections.sort或Arrays.sort),以便对排序顺序进行精确控制。比较器还可以用来控制某些数据结构(如排序集或排序映射)的顺序,或者为没有自然顺序的对象集合提供顺序。
如果你的ADT需要比较大小,或者要放入Collections或Arrays进行排序,可实现Comparator接口并override compare()函数。
接口 Comparable<T>
此接口对实现它的每个类的对象施加总顺序。这种排序称为类的自然排序,而类的compareTo方法称为其自然比较方法。
另一种方法:让你的ADT实现Comparable接口,然后override compareTo() 方法。
与使用Comparator的区别:不需要构建新的Comparator类,比较代码放在ADT内部。
9. CRP原则(7.5.2)
CRP:类应该通过其组合(通过包含实现所需功能的其他类的实例)而不是从基类或父类继承来实现多态行为和代码重用。组合一个对象可以做什么(has_a or use_a) 比扩展它是什么(is_a)更好。
“委托” 发生在object层面,而“继承”发生在class层面。
10. 接口的组合(7.5.2)
使用接口定义系统必须对外展示的不同侧面的行为,接口之间通过extends实现行为的扩展(接口组合),类implements 组合接口,从而规避了复杂的继承关系
12. 白盒框架的原理与实现(7.6)
原理:白盒复用:源代码可见,可修改和扩展,复制已有代码到正在开发的系统,进行修改
白盒框架:通过代码层面的继承进行框架扩展
–通过子类化和重写方法进行扩展
–通用设计模式:模板方法
–子类有main方法,但提供对框架的控制
白盒框架使用子类化/子类型化---继承
–允许扩展每个非私有方法
–需要了解超类的实现
–一次只能延长一次
–一起编译
–通常是所谓的开发人员框架
13. 黑盒框架的原理与实现(7.6)
原理:黑盒复用:源代码不可见,不能修改,只能通过API接口来使用,无法修改代码
黑盒框架:通过实现特定接口/delegation进行框架扩展
–通过实现插件接口进行扩展
–通用设计模式:策略、观察者
–插件加载机制加载插件并控制框架
黑盒框架使用合成--委派/组合
–允许扩展接口中公开的功能
–只需要了解接口
–多个插件
–通常提供更多的模块化
–可以单独部署(.jar,.dll,…)
–通常是所谓的最终用户框架、平台