第九章 面向复用的软件构造技术
第九章 面向复用的软件构造技术
什么是软件复用
-
软件重用是使用现有软件组件实现或更新软件系统的过程。
-
软件复用的两个方面
- 面向复用编程:开发出可复用的软件
- 基于复用编程:利用已有的可复用软件搭建应用系统
特点
-
很大的适应性
-
降低成本和开发时间
-
充分的测试->高可靠
-
标准化、一致化
- GUI 库的重用会在应用程序中产生一致的外观。
-
针对性不强->性能差
-
往往无法拿来就用,需要适配
-
可靠性建立在外部基础上
-
面向复用编程代价高
- 可重用的组件应该以明确定义的、开放的方式设计和构建,具有简洁的界面规范、易于理解的文档,并着眼于未来的使用。
-
基于复用编程的前期代价也高
- 它涉及跨组织、技术和流程的变更,以及支持这些变更的工具成本,以及培训人员使用新工具和变更的成本。
如何使用已有软件进行开发
-
-
建立可复用软件库,对其进行有效管理
-
选择需要的软件
-
进行适配
- 可能必须向组件添加额外的功能。 添加后,新组件可以重新使用
- 可以从组件中删除不需要的功能以提高其性能或减少其空间要求
- 可能必须修改某些组件操作的实现
怎样评测可复用性
-
复用的场合有多少
-
复用的机会有多频繁
-
复用的代价有多大
- 搜索、获取
- 适配、扩展
- 实例化
- 与软件其他部分的互连的难度
好的可复用性软件应该
- 小、简单
- 与标准兼容
- 灵活可变
- 可扩展
- 泛型、参数化
- 模块化
- 变化的局部性
- 在改变需求时稳定
- 丰富的文档和帮助
可复用代码来源
- – Internal (corporate) code libraries 组织的内部代码库(Guava)
- – Third party libraries 第三方提供的库(Apache)
- – Built-in language libraries 语言自身提供的库(JDK)
- – Code samples from tutorials, examples, books, etc. 代码示例
- – Local code guru or knowledgeable colleague 来自同事
- – Existing system code 已有系统内的代码
- – Open source products (be sure to follow any licensing agree 开源软件的代码
复用的级别和形态
复用的级别
-
源代码(方法和具体语句)
-
复制粘贴到自己的程序
-
存在维护问题
- 需要在多个地方更正代码
- 有太多的代码需要处理(很多版本)
-
查找需要代码的网站
- grepcode.com
- github.com/search
- searchcode.com
-
-
模块(类和接口)
-
类是复用的最小单元
- 源代码不重要,可以直接打包成jar包
- 文档很重要
- 封装有助于重用
- 更少的代码需要管理
- 版本管理、向后兼容仍是问题
- 需要将相关的类打包在一起 – 静态链接
-
两种复用方式
-
继承
- 要有严格的父子关系
- 不能取消属性或方法
- 需要仔细设计继承层次
-
委托
-
当一个对象依靠另一个对象来实现它的某些子集的功能时(一个实体将某些东西传递给另一个实体),委托是简单的。
-
委托可以被描述为实体之间共享代码和数据的低级机制。
-
显式和隐式
-
显式委托
- A use B
- 类A和B之间没有什么关联,当A中某个功能需要借助B实现时,将方法参数设置为B类型,通过传入B的对象,调用B的方法来完成。
-
隐式委托
-
A has B
-
在类A中维护着一个类B的对象作为A的成员变量,从而可以调用B的方法,实现复用。
-
类型
-
组合
- 不对外提供改变B类型的成员变量的方法,B类型的成员变量伴随A类型对象创建而创建,A类型对象消失而消失,具有相同的生命周期。
-
聚合
- 在A类型对象创建时并不为B类型对象进行赋值,可随时由外部传入进行赋值,需要调用B中方法时再调用
-
联系
- 先和组合类似,在初始新建B的对象,但同时也像聚合一样提供变值函数
-
-
-
-
-
-
-
库(API)
- 一组提供可重用功能的类和方法 (API)
-
架构层面(框架)
-
一组具体类、抽象类、及其之间的连接关系
-
开发者根据 framework的规约,填充自己的代码进去,形成完整系统
-
将framework看作是更大规模的API复用,除了提供可复用的API,还将这些模块之间的关系都确定下来,形成了整体应用的领域复用
-
-
框架不同于应用程序
- 抽象程度不同,因为框架为一系列相关问题提供了解决方案,而不是单一问题
- 为了适应一系列的问题,框架是不完整的,允许定制。
-
与库的区别:调用关系不同
- 开发者构造可运行软件实体,其中涉及到对可复用库的调用
- Framework作为主程序加以执行,执行过程中调用开发者所写的程序
-
按照扩展的技术划分
-
黑盒框架
-
通过实现特定接口/委派进行框架扩展
-
通过为可插入框架的组件定义接口来实现可扩展性
-
通过定义符合特定接口的组件来重用现有功能
-
这些组件通过委托与框架集成
-
特点
- – 允许扩展暴露在接口中的功能
- – 只需要了解接口
- – 多个插件
- – 通常提供更多的模块化
- – 可以单独部署(.jar、.dll、…)
- – 通常是所谓的最终用户框架、平台
-
-
-
白盒框架
-
通过代码层面的继承进行框架扩展
-
通过继承和动态绑定实现可扩展性
-
特点
- – 允许扩展每个非私有方法
- – 需要理解超类的实现
- – 一次只能扩展一个
- – 一起编译
- – 通常是所谓的开发者框架
-
-
-
-
复用的形态
-
白盒复用
-
源代码可见,可修改和扩展
-
复制已有代码到正在开发的系统,进行修改
-
优点
- 可定制化程度高
-
缺点
- 对其修改增加了软件的复杂度,且需要对其内部充分的了解
-
-
黑盒复用
-
源代码不可见,不能修改
-
只能通过API接口来使用,无法修改代码
-
优点
- 简单,清晰
-
缺点
- 适应性差些
-
设计可复用的类
行为子类型和LSP原则
-
子类型多态
- 客户端可用统一的方式处理不同类型的对象
-
行为子类型
-
Java 中的编译器强制规则(静态类型检查)
- 子类型可以增加方法,但不可删除方法
- 子类型需要实现抽象类型中的所有未实现方法
- 子类型必须返回相同类型或者子类型(协变)
- 子类型必须接受相同类型(Java中不支持逆变)
- 子类型中重写的方法不能抛出额外的异常
-
也适用于指定的方法
-
更强的不变量
不要改父类RI(否则可能行为不一致),可以增加自己的RI,增加自己的RI后要保证父类中没有方法破坏自己的RI,如果有要重写
-
更弱的前置条件
-
更强的后置条件
-
-
LSP原则(强行为子类型化)
- 前置条件不能强化
- 后置条件不能弱化
- 不变量要保持
- 子类型方法参数:逆变
- 子类型方法的返回值:协变
- 异常类型:协变
-
协变和逆变
-
协变
- 父类型->子类型:越来越具体specific
-
逆变
- 子类型->夫类型:越来越抽象abstract
-
会调用valueOf(),无法接收
-
-
泛型中的LSP
-
泛型的继承关系
-
-
只有尖括号中存在包含关系时才是父子关系(静态检测) -
-
-
类型擦除
- 类型参数的类型信息在代码编译完成后被编译器丢弃,因此这些类型信息在运行时是不可用的。
- 在通用类型中用它们的边界或Object(如果类型参数没有边界)替换所有类型参数。因此,产生的字节码只包含普通的类、接口和方法
-
-
委派
-
实现排序的两种方法
实现Comparator接口并override compare()函数
让ADT 实现 Comparable 接口,然后 override compareTo() 方法。与使用Comparator的区别:不需要构建新的Comparator类,比较代
码放在ADT内部。
-
一个类不需要继承另一个类的全部方法,通过委托机制调用部分方法,从而避免大量无用的方法
-
“委托”发生在object层面,而“ 继承 ”发生在 class层面
-
-
-
-
委派的三种类型
-
Use(A use B)
- 临时性的delegation
-
Association联系 (A has B)
- 永久性的delegation
- 子主题 2
-
Composition/aggregation (A owns B)
-
Composition/Aggregation是Association的两种具体形态
-
Composition组合
- 更强的association,但难以变化
-
Aggregation聚合
- 更弱的association,可动态变化
-
-