文章目录
一、可复用性的度量,形态与外部表现
1、软件复用分类
- 面向复用编程:开发出可复用软件
- 基于复用编程:利用已有复用软件搭建应用系统
模块越小,复用范围越大
2、复用层次
(1) 代码层面:最主要
- 白盒复用:源代码可见,可修改,可扩展
典型做法:复制已有代码 - 黑盒复用:源代码不可见,不能修改
典型做法:通过API接口来使用
(2)模块层面的复用
- 继承inheritance
委托delegation
(3)API/Package层面的复用
- 远程端API
开发者调用可复用库(API)
(4) Framework框架复用:
- 一组类,抽象类,及之间的连接关系。
- Framework预留接口,开发者根据Framework规约填充自己的代码
- Framework调用开发者所写的程序
- 白盒框架:通过代码层面的继承进行框架扩展
黑盒框架:通过实现特定接口/delegation进行框架扩展
library 和 Framework 为系统层面的复用
(3)任何实体都可以复用
二、面向复用的软件构造技术
1、LSP(Liskov 替换准则)
(1)子类型多态
- 客户端可以使用统一的方式处理不同类型的对象
- 子类型的变量可以代替父类型的变量
任何使用a的场景都可以用c1和c2代替:
Animal a = new Animal();
Animal c1 = new Cat();
Cat c2 = new Cat();
(2) 规则:
- 子类型只能增加方法,但不能删除方法
- 子类型必须实现父类型中所有未实现方法
- 子类型中重写的方法,参数类型与父类相同(或符合逆变 co-variance,即为其父类型);返回值类型与与父类型相同,或为父类型返回值的子类型
- 子类型中重写的方法不能抛出额外异常。异常:协变
- 更强的不变性RI(不变量要保持),前置条件变弱,后置条件变强
如果父类型是不可变的,则所有子类型也应为不可变的
协变:返回值类型,异常类型变为原来的子类型
逆变:参数类型变为原来的父类型
Java不支持逆变,java遇到逆变看成overload
(3)泛型中的LSP
- 泛型不支持协变
List<Integer> myInts = new ArrayList<>();
Number为Integer的父类型
List<Number> myNum = myInts; // 错误!! - 通配符?,表示任何类型的父类型
public static void printList(List<?> list){…}
List l1 = Arrays.asList(1,2,3);
printList(l1); // 可以
2、委派和组合
(1)委派
- 一个对象请求实现另一个对象的功能
(2)委派和继承区别
- 委派不用该类型的所有方法;发生在object层面
继承要用该类型的所有方法:发生在class层面
(3)委派的分类
- 临时性的委派:依赖关系 ,UML表示----->
在类的某个方法中,参数为另一个类,方法内操作调用这个参数类的方法 - 永久性委派:关联关系,UML表示——>
另一个类为该类的一个属性
class Duck{
Flyable f = new CannotFly();;
public Duck(Flyable f){
this.f = f;
}
} - 更强的委派
class Duck{
Flyable f = new FlyWithWings();
public Duck(){
}
public fly(){
this.f.fly();
}
} - 更弱的委派,可动态变化
class Duck{
Flyable f ;
public Duck(Flyable f){
this.f = f;
}
}
3、Framework设计
- 典例:程序扩展
- 白盒框架:继承关系 override
黑盒框架:delegation
三、面向复用的设计模式
(一)结构型模式
1、适配器模式 Adapter
- 将某个类/接口转化成客户期望的其他形式,通过增加一个接口,将已有的子类封装起来,客户面向该接口编程
2、装饰器模式 Decorator
- 每个子类实现不同特征,特征的任意组合
使用继承会造成组合爆炸,大量代码重复 - 为对象增加不同侧面的特征,每个特征构造子类,通过委派增加到对象上
具体代码例子:
/*
* ADT接口定义
*/
public interface Stack{
public void push(Item e);
public Item pop();
}
/*
* 共性/最基本功能实现
*/
public class ArrayStack implements Stack{
......
@Override
public void push(Item e){
操作...
}
@Override
public Item pop(){
操作...
}
}
/*
* 增加一个用于decorator 的基础抽象类
*/
public abstract class StackDecorator implements Stack{
protected final Stack stack; // 只能子类使用,且为final
public StackDecorator(Stack stack){
this.stack = stack; // 委派
}
@Override
public void push(Item e){
this.stack.push(e);
}
@Override
public Item pop(){
this.stack.pop();
}
}
/*
* 具体实现类,其中可以在原来的共性方法上增加新特性
* 可以增加新方法
*/
public class UndoStack extends StackDecorator implements Stack{
private final Undolog log = new Undolog();
public UndoStack(Stack stack){
super(stack);
}
@Override
public void push(Item e){
log.append(e); // 增加新的操作
super.push(e);
}
// 增加新的方法
public void undo(){
...
}
}
/*
* 客户端使用,套马甲
*/
Stack t = new UndoStack(new ArrayStack());
Stack t1 = new SyschesStack(new UndoStack(new ArrayStack()));
3、外观模式 Facade
- 客户端通过一个简化的接口静态方法来访问复杂系统内的功能。对复杂系统做封装
(二)行为类模式
1、策略模式 Strategy
- 一个任务有不同算法,客户端根据需要切换动态算法
为不同算法构造抽象接口,利用委派,运行时动态传入 - 例子:两种不同结算方式
方式1:
方式2:
支付行为类:
客户端:
2、模板模式 Template Method
- 做事情的步骤一样,但具体方法不一样
- 使用继承和重写实现
3、Iterator 迭代器模式
(1)Iterable 接口:实现接口的集合对象是可迭代遍历的
// 需要自己写的方法
public interface Iterable<T>{
Iterator<T> iterator(); //接口:迭代器
}
public interface Iterator<E>{
public boolean hasNext();
public E next();
public void remove();
}
- 构造自己的可迭代类:让自己的集合类实现Iterable接口,并实现自己的独特Iterator迭代器(hasNext, next, remove)
public class Pair<E> implements Iteratable<E>{
private final E first, second;
public Pair(E f, E s) { first = f; second = s; }
@Override
public Iterator<E> iterator(){
return new PairIterator();
}
private class PairIterator implements Iterator<E>{
private boolean seenFirst = false, seenSecond = false;
@Override
public boolean hasNext() { return !seenSecond; }
@Override
public E next() {
if(!seenFirst){seenFirst = true; return first;}
if (!seenSecond) { seenSecond = true; return second;}
throw new NoSuchElementException();
}
@Override
public void remove(){
throw new UnsupportedOperationException();
}
}
}
//此时,可以按照属性遍历类Pair
Pair<String> pair = new Pair<String>("foo", "bar");
for (String s : pair) { ... }