面向复用的软件构造技术
什么是软件复用?
软件重用是使用现有软件组件来实现或更新软件系统的过程。
软件复用的两个视角
1.创建:以系统的方式创建可重用资源(为重用而编程面向复用编程:开发出可复用的软件)
2.使用:将资源重新用作创建新系统的构建块(使用重用进行编程基于复用编程:利用已有的可复用软件搭建应用 系统)
为什么要重复使用?
1.“创造可重复使用而非暂时性文物的动力有美学、智力和经济动机,也是人类追求永生的愿望的一部分。
2.它将人类与其他生物区分开来,将文明社会与原始社会区分开来”(Wegner,1989)。
优缺点
1.重复使用具有成本效益和及时性降低成本和开发时间
通过缩短软件生产周期提高软件生产效率(软件开发速度更快,人员更少)
不浪费资源不必要地“重新发明轮子”
降低维护成本(可以生产出质量更好、更可靠、更高效的软件)
2.重用产生可靠的软件
测试,可靠、稳定
重用已经存在一段时间并经过调试的功能是在稳定的子系统上构建的基础
3.重用产生标准化,在不同应用中保持一致
GUI库的重用在应用程序中产生了通用的外观。
与常规、连贯的设计保持一致。
重用成本高昂:它涉及组织、技术和流程的变化,以及支持这些变化的工具成本,以及培训人员了解新工具和变化的成本
开发可复用的软件
高于一般软件的成本:要有足够高的适应性
性能差一些:针对更普适场景,缺少足够的针对性
可复用软件库,对其进行有效的管理,但往往不能直接使用,需要适配
如何衡量“可重用性”?
主要是复用频率与复用代价
可重复使用组件的水平和形态
最主要的复用是在代码层面,但软件构造过程中的任何实体都可能被复用,比如需求,数据,规约
白盒复用
源代码可见,可修改和扩展
复制已有代码到正在开发的系统,进行修改
可定制化程度高
其修改增加了软件的复杂度,且需要对其内部充分的了解
黑盒复用
源代码不可见,不能修改
只能通过 API 接口来使用,无法修改代码
简单清晰但是适应性差一点
源代码重用
复制所有代码是很低级的重用,存在风险与问题
模块级重用:类/接口
重用类:类是代码重用的原子单元
不需要源代码,类文件或jar/zip
只需要包含在类路径中
可以使用javap工具获取类的公共方法头
重用类的方法:继承类
Java提供了一种名为“继承”的代码重用方式
类扩展了现有类的属性/行为
此外,它们可能会覆盖现有行为
重用类的方法:委托
委派只是指一个对象在其功能的某个子集上依赖于另一个对象(一个实体将某些东西传递给另一个实体)例如,分拣机正在将功能委托给某个比较器
明智的委派实现代码重用
排序器可以与任意排序顺序重复使用
比较器可以与需要比较整数的任意客户端代码一起重用
显式委托:将发送对象传递给接收对象
隐式委派:根据语言的成员查找规则
委派可以被描述为在实体之间共享代码和数据的低级别机制。
库级重用:API/包
库:一组提供可重复使用的类和方法(API)功能
Framework:可重用的骨架代码,可以自定义为应用
一个好的API的特点
易于学习
易于使用,即使没有文档
难以滥用
易于阅读和维护使用它的代码
足够强大以满足要求
易于发展
适合观众
系统级重用:框架
框架:一组具体类、抽象类、及其之间的连接关系
开发者根据 framework的规约,填充自己的代码进去,形成完整系统
将framework看作是更大规模的API复用,除了提供可复用的API,还将这 些模块之间的关系都确定下来,形成了整体应用的领域复用
框架设计
框架与应用程序不同,抽象级别是不同的,因为框架为一系列相关问题提供了解决方案,而不是单个问题。为了适应这一系列问题,该框架是不完整的,包含了热点和钩子以允许自定义
框架可以根据用于扩展它们的技术进行分类。为白盒框架,黑盒框架
白盒框架白盒框架,通过代码层面的继承进行框架扩展
-通过继承和动态绑定实现的可扩展性。
-现有的功能是通过子类化框架基类和覆盖预定义的钩子方法来扩展的
-通常,设计模式(如模板方法模式)用于覆盖钩子方法。
Blackbox框架黑盒框架,通过实现特定接口/代表团
-通过为可插入框架的组件定义接口来实现可扩展性。
-通过定义符合特定接口的组件来重用现有功能
-这些组成部分通过授权与框架相结合。
设计可重用类
行为亚型与Liskov替代原则(LSP)
子类型多态:客户端可用统一的方式处理不同类型的对象
子类型可以增加方法,但不可删
子类型需要实现抽象子类型需要实现抽象
子类型中重写的方法 必须有相同或子类型的返回值或者符合co-variant的参数
子类型中重写的 方法必须使用同样类型的参数或者符合contra-variant的参数(此种情况Java目 前按照重载overload处理)
子类型中重写的方 法不能抛出额外的异常
也适用于指定的行为(方法):
-相同或更强的不变量
-相同或较弱的先决条件
-相同或更强的后置条件
利斯科夫替代原则
LSP是一种亚型关系的特殊定义,称为(强)行为亚型强行为子类型化
在编程语言中,LSP依赖于以下限制:
前置条件不能强化
后置条件不能弱化
不变量要保持
子类型方法参数:逆变
子类型方法的返回值:协变
异常类型:协变
协变
父类型 → 子类型:越来越具体 specific 返回值类型:不变或变得更具体 异常的类型:也是如此。
反协变、逆变
父类型→子类型:越来越具体specific 参数类型:要相反的变化,要不变或越来越抽象
什么是类型擦除
虚拟机没有通用类型的对象-所有对象都属于普通类。虚拟机中没有泛型类型对象-所有对象都属于普通类!
泛型类型信息在运行时被擦除(即仅在编译时)
每当定义泛型类型时,都会自动提供相应的原始类型。原始类型的名称只是泛型类型的名称,去掉了类型参数。定义泛型类型时,会自动提供一个对应的原始类型(非泛型类型),原始类型的名字就是去掉类型参数后的 泛型类型名。类型变量将被擦除并替换为其边界类型(或无边界变量的对象)擦除时类型变量会被擦除,替换为限定类型,
如果没有限定类型则替换为对象
委托和构成
如果你的ADT需要比较大小,或者要放入Collections或Arrays中 进行排序,可实现Comparator接口并override compare()函数。
另一种方法:让你的ADT实现Comparable接口,然后override compareTo() 方法
与使用Comparator的区别:不需要构建新的Comparator类,比较代 码放在ADT内部。
委托
委派只是指一个对象在其功能的某个子集上依赖于另一个对象(一个实体将某些东西传递给另一个实体)委派/委托:一个对象请求另一个对象的功能,例如,分拣机正在将功能委托给某个比较器
明智的委派实现代码重用委派是复用的一种常见形式
-排序器可以与任意排序顺序重复使用
-比较器可以与需要比较整数的任意客户端代码一起重用
委派可以被描述为在实体之间共享代码和数据的低级别机制。
-显式委托:将发送对象传递给接收对象
-隐式委派:根据语言的成员查找规则
很多设计模式将继承和委托结合使用
用委派替换继承
问题:您有一个子类,它只使用其超类的一部分方法(或者不可能继承超类数据)。如果子类只需要复用父类中的一小部分方法
解决方案:创建一个字段并在其中放入一个超类对象,将方法委托给该超类对象并消除继承
要使用继承,而是通过委派机制来实现
从本质上讲,这种重构会拆分两个类,并使超类成为子类的辅助对象,而不是其父类。
-子类将只有必要的方法来委托给超类对象的方法,而不是继承所有的超类方法。一个类不需要继承另一个类的全部方法,通过委托机制调用部分方法
-类不包含从超类继承的任何不需要的方法。从而避免继承大量无用的方法
复合超越继承原理
组合优先于继承,注:组合是委派的一种形式。
“委托”发生在 object 层面,而“继承”发生在 class 层面
通过inheritance实现对某些通用行为的复用 缺点:需要针对“飞法”设计复杂的继承关系树;不能 同时支持针对“叫法”的继承,二者的继承逻辑关系可 能不同;动物行为发生变化时,继承树要随之变化。
1.使用接口定义系统必须对外 展示的不同侧面的行为
2.接口之间通过extends实现 行为的扩展(接口组合)
3.类implements组合接口
4.从而规避了复杂的继承关系
依赖关系:临时的委托
使用类的最简单形式是调用其方法;
两个类之间的这种形式的关系被称为“使用-一种”关系,其中一个类使用另一个类,而没有将其实际合并为属性-例如,它可以是一个参数或在方法中本地使用。通过方法的参数或者在方法的局部中使用发生 联系
依赖关系:一个对象需要其他对象(供应商)来实现它们的临时关系。
联系:永久性委托
关联:对象类之间的持久关系,允许一个对象实例使另一个对象执行代表它的操作。
-hasa:一个类有另一个作为属性/实例变量
这种关系是结构性的,因为它指定一种对象与另一种对象相连接,而不表示行为。
构成:更多的联系,但难以变化
组合是一种将简单对象或数据类型组合成更复杂对象或数据的方法。
-is_part_of:一个类有另一个作为属性/实例变量-实现为一个对象包含另一个对象。
聚合:更弱的关联,但可以变化
聚合:对象存在于另一个对象之外,是在外部创建的,因此它作为参数传递给构造器。
设计系统级可重用API库和框架
为什么API设计很重要
API是程序员最重要的资产和“荣耀”,吸引外 部用户,提高声誉
好的代码是模块化的,每个模块都有一个API,用户投入巨大:获取、编写、学习,从API角度思考提高了代码质量,成功的公共API吸引了用户
也可能是您最大的责任之一,糟糕的API可能会导致无休止的支持电话流,可能会阻碍前进的能力
公共API是永恒的,只有一次写对的机会,模块一旦有用户,就不能随意更改API
建议:始终以开发API的 标准面对任何开发任务 面向“复用”编程 而不是面向“应用”编程
难度:要有足够良好的设 计,一旦发布就无法再自 由改变
白盒与黑盒框架
白盒框架使用子类化/子类型化-
-允许扩展每个非专用方法
-需要了解超类的实现
-一次只能扩展一个
-一起编译
-通常所谓的开发人员框架
黑盒框架使用组合
-允许扩展接口中公开的功能
-只需要了解界面
-多个插件
-通常提供更多的模块化
-可以单独部署(.jar、.dll、..)
-通常称为最终用户框架、平台