大纲
什么是软件复用?
如何衡量“可复用性”?
可复用组件的级别和形态
- 源代码级别复用
- 模块级别的复用:类/抽象类/接口
- 库级别的复用:API /包
- 系统级别的复用:框架
对可复用性的外部观察
- 类型变化
- 例行分组
- 实施变更
- 代表独立
- 分解常见行为
总结
什么是软件复用?
软件复用
软件复用是使用现有软件组件实施或更新软件系统的过程。
软件复用的两个观点
- 创建:以系统方式创建可复用资源(面向复用编程:开发出可复用的软件)
- 使用:复用资源作为创建新系统的构建块(基于复用编程:利用已有的可复用软件搭建应用系统)
为什么复用?
- “创造可重复使用而不是暂时性文物的动力具有审美和知识以及经济动机,并且是人类对不朽渴望的一部分。
- 它将人与其他生物区分开来,并从原始社会文明“(Wegner,1989)。
为什么复用?
重复使用具有成本效益,并且具有及时性降低成本和开发时间
- 通过缩短软件生产周期时间来提高软件生产力(开发速度更快,人员更少的软件)
- 不浪费资源不必要地“重新发明轮子”
- 降低维护成本(可生产更好的质量,更可靠和更高效的软件)
经过充分测试,可靠,稳定,生成可靠的软件
- 重复使用已经存在一段时间并被调试的功能是构建稳定子系统的基础
重复使用标准化,在不同应用中保持一致
- 复用GUI库可在应用程序中产生常见的外观。
- 与常规,一致的设计保持一致。
重复使用成本
可复用组件的设计和构建应该采用明确,开放的方式,具有简洁的接口规范,易于理解的文档以及面向未来的使用。做到这些,需要代价。
复用成本很高:涉及跨越组织,技术和流程变更,以及支持这些变更的工具成本,以及培训人员使用新工具和变更的成本。 不仅面向复用编程代价高,基于复用编程代价也高
面向复用开发:开发可复用的软件
可复用组件的开发成本高于特定等价物的成本。 这种额外的可复用性增强成本应该是一个组织而不是项目成本。 开发成本高于一般软件的成本:要有足够高的适应性
通用组件的空间效率可能较低,执行时间可能长于其特定的等效项。 性能差些:针对更普通场景,缺少足够的针对性。
基于复用开发:使用已有软件进行开发
必须开发和维护用于体系结构,设计,文档和代码的组件管理工具,例如存储库。可复用软件库,对其进行有效的管理
一个关键问题:适应往往无法拿来就用,需要适配
- 可能需要将额外的功能添加到组件。 添加完成后,新组件可以重新使用。
- 可以从组件中删除不需要的功能,以提高其性能或减少其空间要求
- 某些组件操作的实现可能需要修改。
如何衡量“可复用性”?
衡量可复用性
软件资产在不同应用场景中的重复使用频率如何?
- 资产使用的可能性越高,其可复用性越高。
- 编写一次,重复使用多次。
为重复使用此资产需支付多少费用?
- 购买资产和其他强制性库的成本
- 调整和扩展它的成本
- 实例化它的成本
- 更改与其交互的系统的其他部分的成本
可复用性
可复用性意味着对构建,打包,分发,安装,配置,部署,维护和升级问题进行一些明确的管理。
具有高可复用性的软件资产应该:
- 简短(小尺寸)和简单(低复杂度)
- 便携式和标准合规性
- 适应性和灵活性
- 可扩展性
- 通用和参数化
- 模块化
- 易变(可变)设计假设的本地化
- 改变要求时的稳定性
- 丰富的文档
可重复使用组件的级别和形态
复用水平
可复用组件可能是代码
- 最普遍:大多数程序员与复用有关
但是,从更广泛和更高层次上看待可以重复使用的观点带来的好处。
- 要求
- 设计和规格
- 数据
- 测试用例
- 文件
我们在这节课中关注的是什么
源代码级别:方法,语句等
模块级别:类和接口
库级别:API
- Java库,.jar
架构级别:框架框架
代码复用的类型
白盒复用:源代码可见,可修改和扩展
- 代码本身可用时复用代码。 通常需要某种修改或改编
- Pro:您可以自定义模块以适应特定的情况,这允许在更多情况下复用
- Con:你现在拥有自定义的结果,所以它增加了你的代码复杂度。 您需要内部组件的内在知识。
黑盒复用:源代码不可见,不能修改
- 通过提供一些“胶水”来重新组合现有代码的形式,但不必更改代码本身 - 通常是因为您无法访问代码。只能通过API接口来使用,无法修改代码
- Pro:简洁和清洁
- Con:很多时候这是不可能的
可复用组件分布的格式
形式:
- 源代码
- 包,如.jar,.gem,.dll,
可复用软件组件的来源:
- 组织的内部代码库(Guava)
- 第三方提供的库(Apache)
- 语言自己提供的库(JDK)
- 来自教程,示例,书籍等的代码示例
- 本地代码专家或知识渊博的同事
- 现有的系统代码
- 开源产品(请务必遵守任何许可协议)
(1)源代码复用
复用代码 - 最低级别
将部分/全部复制/粘贴到您的程序中
维护问题
- 需要在多个地方更正代码
- 使用太多的代码(很多版本)
过程中出现错误的风险很高
可能需要知道如何使用的软件工作
需要访问源代码
(2)模块级复用:类/接口
复用类
类是代码复用的原子单元
- 不需要源代码,类文件或jar / zip
- 只需要包含在类路径中
- 可以使用javap工具来获取类的公共方法头
文档非常重要(Java API)
封装有助于复用
代码管理较少
版本控制,向后兼容性仍然存在问题
需要一起打包相关的类 - 静态链接
复用类的方法:继承
Java提供了一种名为Inheritance的代码复用方法
- 类扩展现有类的属性/行为
- 另外,他们可能会覆盖现有的行为
不需要放置仅仅转发或委托工作的虚拟方法
更好地捕捉现实世界
通常需要在实现之前设计继承层次结构
无法取消属性或方法,因此一定要小心,不要过分
复用类的方法:委托
委托只是当一个对象依赖另一个对象来实现其功能的某个子集时(一个实体将某个事物传递给另一个实体)
- 例如分拣机正在委托比较器的功能
审慎的委托支持代码复用
- 分拣机可以重复使用任意的排序顺序
- 比较器可以重复使用需要比较整数的任意客户端代码
显式委托:将发送对象传递给接收对象
隐式委托:由语言的成员查找规则
委托可以被描述为在实体之间共享代码和数据的低级机制。
(3)库级复用:API /包
库:一组提供可复用功能的类和方法(API)
框架:可以定制到应用程序中的可重复使用的框架代码
框架调用回客户端代码
- 好莱坞原则:“不要打电话给我们。 我们会打电话给你。“
一般区别:库与框架
框架作为主程序加执行,执行过程中调用开发者所写的程序
开发者构造可运行软件实体,其中涉及到对可复用库的调用
(4)系统级复用:框架
应用程序框架
框架是子系统设计,包含一系列抽象和具体类以及每个类之间的接口
框架:一组具体类,抽象类,及其之间的连接关系 - 只有“骨架”,没有“血肉”
框架是一种抽象,其中提供通用功能的软件可以通过额外的用户编写的代码进行选择性更改,从而提供特定于应用程序的软件。 开发者根据框架的规约,填充自己的代码进去,形成完整系统
可复用性利用了应用领域知识和经验丰富的开发人员的先前努力
- 数据处理,图形用户界面等
- 将框架看作是更大规模的API复用,除了提供可复用的API,还将这些模块之间的关系都确定下来,形成了整体应用的领域复用
框架:领域复用
系统通过添加组件来填充缺失的设计元素并实例化抽象类来实现
- 通常通过选择性覆盖来扩展框架; 或者程序员可以添加专门的用户代码来提供特定的功能---定义从抽象类祖先继承操作的具体类
- 钩子方法,被应用程序覆盖以扩展框架。 钩子(Hook)方法系统地将应用程序域的接口和行为与应用程序在特定上下文中所需的变体解耦。
- 控制反转:与库或标准用户应用程序不同,控制流不是由调用者决定的,而是由框架决定的。
- 不可修改的框架代码:框架代码不应该被修改,同时接受用户实现的扩展。 换句话说,用户可以扩展框架,但不应修改其代码。
控制反转(Inverse of Control)
由第三方的容器来控制对象之间的依赖关系,而非传统实现中由代码直接操控。
控制权由代码中转到了外部容器,带来的好处就是降低了对象之间的依赖程度,提高灵活性和可维护性。
框架设计
框架与应用程序不同
- 抽象级别不同,因为框架为相关问题家族提供解决方案,而不是单一解决方案。
- 为了适应这一系列问题,该框架是不完整的,包含热点和挂钩以允许定制
框架可以用扩展它们的技术来分类。
- 白盒框架
- 黑盒框架
白盒和黑盒框架
白盒框架:
- 通过继承和动态绑定实现的可扩展性。
- 通过继承框架基类并重写预定义的钩子方法来扩展现有功能
- 通常使用模板方法模式等设计模式来覆盖钩子方法。
黑盒框架
- 通过为可插入框架的组件定义接口来实现可扩展性。
- 通过定义符合特定接口的组件来复用现有功能
- 这些组件通过委托与框架集成。
类库与框架
类库:
- 较少的域特定
- 提供更小的复用范围。
- 类库是被动的; 对控制流程没有限制。
框架:
- 类为相关应用系列合作。
- 框架活动; 影响控制流程。
在实践中,开发人员经常使用两种:
- 框架通常在内部使用类库来简化框架的开发。
- 框架事件处理程序使用类库来执行基本任务(例如字符串处理,文件管理,数值分析......)。
组件与框架
组件
- 类的独立实例
- 连接在一起形成完整的应用程序。
- 黑盒定义了一套连贯的操作,
- 可以基于接口的语法和语义使用。
- 组件甚至可以在二进制代码级复用。
•优点是应用程序不一定需要在组件更改时重新编译。
构架:
- 经常用于开发组件
- 组件通常插入黑盒框架。
可复用性的外部观察
对可复用性的外部观察
类型可变
功能分组
实现可变
表示独立
共性抽取
类型可变
可复用组件应该是类型参数化的,以便它们可以适应不同的数据类型(输入,计算和输出);
- 可重复使用的模块应该适用于许多不同类型的元素,而不需要开发人员对软件文本进行手动更改。
换句话说,我们需要一个用于描述类型参数化模块的工具,这个模块也被称为通用模块。
通用性:可复用组件应该是通用的。
类型可变(泛型):适应不同的类型,且满足LSP
实现可变
在实践中有很多种适用的数据结构和算法。
我们不能期望单个模块能够处理所有可能性,这种变化确实是这样; 这将是巨大的。
我们需要一系列模块来涵盖所有不同的实现。
实现可变:ADT有多种不同的实现,提供不同的表示和抽象功能,但具有同样的规范(前置条件,后置条件,不变式),从而可以适应不同的应用场景
功能分组
一个自给自足的可复用模块需要包含一组功能,每个操作一个功能。
完整性
提供完备的细粒度操作,保证功能的完整性,不同场景下复用不同的操作(及其组合)
表示独立性
可复用模块的一般形式应该使客户能够指定一个操作而不知道它是如何实现的。
表示独立性是信息隐藏规则的延伸,对于大型系统的顺利开发至关重要:实施决策经常会改变,客户应该受到保护。 内部实现可能会经常变化,但客户端不应受到影响。
表示独立反映了客户对可复用性的看法 - 忽略内部实现细节和变体的能力
表示独立性,信息隐藏
共性抽取
分解共同行为,反映了供应商的观点,更一般地反映了可复用类的开发者的观点。
目标是利用家庭或实施子系列中可能存在的任何通用性。
将共同的行为(共性)抽象出来,形成可复用实体
如上所述,在某些问题领域可用的各种实现通常需要基于模块族的解决方案。
这些类别中的每一个都涵盖了许多变体,但通常可以在这些变体之间找到显着的共性。
总结
什么是软件复用?
如何衡量“可复用性”?
可复用组件的级别和形态
- 源代码级别复用
- 模块级复用:类/接口
- 库级:API /包
- 系统级复用:框架
对可复用性的外部观察
- 类型可变
- 功能分组
- 实现可变
- 表示独立
- 共性抽取