4-1可复用性的度量、形态与外部表现
复用的级别:
源代码级别的复用
模块级别的复用:类/抽象类/接口
库级别的复用:API/包
系统级别的复用:框架
什么是软件复用
面向复用编程:开发出可复用的软件
基于复用编程:利用已有的可复用软件搭建应用系统
复用的作用:
降低成本和开发时间
经过充分 测试,可靠、稳定
标准化,在不同应用中保持一致
但支持复用性越强的软件开发成本同时越高,性能差些: 针对更普适场景,缺少足够的针对性
使用已有软件进行开发:
可复用软件库,对其进行有效的管理
往往无法拿来就用,需要适配
如何度量可复用性
复用的机会有多频繁?复用的场合有多少?
复用的代价有多大?
获取
适配
实例化
与软件其他部分的互连的难度
易于复用的软件的特点:
小、简单
与标准兼容
灵活可变
可扩展
泛型、参数化
模块化
变化的局部性
稳定
丰富的文档和帮助
可复用组件的层次和形态
最主要的复用是在代码层面,但需求,spec,测试用例等等也可以复用。
白盒复用:源代码可见,可修改和扩展,需要对其内部充分的了解
黑盒复用:源代码不可见,不能修改 ,只能通过API接口来使用,无法修改代码 ,适应性差些
源代码复用
相关研究1:如何从互联网上快速找到需要的代码片段?
反向研究:如何从源代码中检测出克隆代码(clone code)?
模块级复用(类,接口)
继承
使用
组成/聚合
委托/关联
委托:一个对象依赖于另一个对象的部分功能
显式委托:将发送对象传递给接收对象
隐式委托:根据语言的成员查找规则
库级别的复用:API/包
库和框架的区别:
开发者构造可运行软件实体,其中涉及到对可复用库的调用
Framework作为主程序加以执行,执行过程中调用开发者所写的程序
系统级别的复用:框架
框架:一组具体类、抽象类、及其之间的连接关系,开发者根据 framework的规约,在Framework预留的接口填充自己的代码进去,形成完整系统
白盒框架,通过代码层面的继承进行框架扩展
黑盒框架,通过实现特定接口/delegation进行 框架扩展
可重用性的外部观察
类型可变
功能分组
实现可变
表示独立
共性抽取
类型可变(泛型):适应不同的类型,且满足LSP
实现可变:ADT有多种不同的实现,提供不同的representations和abstract function,但功能分组:具有同样的specification (pre-condition, postcondition, invariants),从而可以适应不同的应用场景
提供完备的细粒度操作,保证功能的完整性,不同场景下复用不同的操作(及其组合)
表示独立:内部实现可能会经常变化,但客户端不应受到影响。
共性抽取:将共同的行为(共性)抽象出来,形成可复用实体:父类、抽象类
4-2面向复用的软件构造技术
设计可复用的类 :
继承与重写
重载
参数多态与泛型编程
行为子类型 与Liskov替换原则 (LSP)
组合与委托
设计可复用的类
LSP
子类型多态:客户端可用统一的方式处理 不同类型的对象
例:设Cat是Animal的子类
都是对的。
LSP原则:
子类型可以增加方法,但不可删
子类型需要实现抽象类型 (接口、抽象类)中所有未实现的方法
子类型中重写的方法必须有相同或子类型的返回值或者符合co-variant的参数
子类型中重写的方法必须使用同样类型的参数或者符合contra-variant的参数(此种情况Java目前按照重载overload处理)
子类型中重写的方法不能抛出额外的异常
或者概括为:
更强的不变量
更弱的前置条件
更强的后置条件
子类型方法参数:逆变
子类型方法的返回值:协变
异常类型:协变
协变:
父类型→子类型:越来越具体specific
返回值类型:不变或变得更具体
异常的类型:不变或变得更具体
逆变:
父类型→子类型:越来越具体specific
参数类型:不变或变得更抽象
目前Java中遇到这种情况(参数逆变和协变),当作overload看待(JAVA不支持)
Java中 数组是协变的: 对T[]数组,可以保存类型T及其子类型的数据
泛型是类型不变的 ,类型参数在编译后被丢弃, 在运行时不可用 。
即,泛型在编译时确定具体类型,在运行时虚拟机中已没有泛型概念。(类型擦除)
The class Class 类型类
Java在运行时,为所有对象维护一 个运行时类型标识,这个标识跟踪对象所属的类,用来确定选择哪个方 法运行。保存这些信息的类叫做“Class类型类”。注意:Class是类的名字,不是关键词class。每个”Class”的对象描述了一个类的信息。
泛型中的通配符:
无限定通配符? ,List<?>,泛型可以是任意类型
方法的实现不依赖于类型参数