第九讲 面向复用的软件构造技术
9.1 复用代码的层级
源代码、模块(类、接口)、库(包)、系统(框架)
-
白盒复用:源代码可见,可修改和扩展,但需要对其充分了解。
-
黑盒复用:源代码不可见,只能通过API调用,适应性较差,但简单清晰。
-
白盒框架,通过代码层面的继承进行框架扩展
-
黑盒框架,通过实现特定接口/delegation进行框架扩展
9.2 设计复用类
封装、信息隐藏、继承重写、多态、重载、泛型、子类型、委派
9.3 LSP原则(行为子类型)
-
子类型可以增加方法,但不可删
-
子类型需要实现抽象类型中的所有未实现方法
-
子类型中重写的方法必须有相同类型的返回值或者符合co-variance的返回值
-
子类型中重写的方法必须使用同样类型的参数或者符合contra-variance的参数**(Java不支持,目前当做重载来看待)**
-
子类型中重写的方法不能抛出额外的异常,抛出相同或者符合co-variance的异常(不抛出也合法)
-
更强的不变量、更弱的前置条件、更强的后置条件
-
协变:更为具体化的子类;反协变(逆变):更为抽象的父类。
数组的子类型
以父类型声明的数组可以存储子类型元素。
泛型的子类型
List<String> is not a subtype of List<Object>(需要特别注意)
若想使用泛型的子类型,必须使用通配符“?” 见7.3多态
9.4 委派(Delegation)
一个对象请求另一个对象的功能。
- (USE)临时委派:调用方法时传入一个类,这个方法再用这个类中的方法得到结果。
- (HAS)Association 永久委派:构造函数的时候传入类。
- (OWN)Composition:更强的Association,难以变化,直接在类初始化中指定。
- Aggregation:更弱的Association,提供设置委派类的方法。
如果子类只需要复用父类中的一小部分方法,可以不需要使用继承,而是通过委派机制来实现!
Stack不应该有List的公开方法(add\remove),但可以通过List实现。
9.5 CRP原则
委派是平等的,而继承是有父子关系的。
CRP原则更倾向于使用委派而不是继承来实现复用。
委派模式:通过运行时动态绑定,实现对其他类中代码的动态复用。
9.6 比较器
Interface Comparator<T>——比较器
int compare(T o1, T o2):
arr.sort((c1, c2) -> {
double v1 = statistics.get(c1);
double v2 = statistics.get(c2);
int compareResult = ((Double)v2).compareTo(v1);
if (compareResult == 0) {
return rejc.get(c1).compareTo(rejc.get(c2));
}
return compareResult;
});
Interface Comparable<T> ——实现可比较的类
public int compareTo(T o)
public class Edge implements Comparable<Edge> {
Vertex s, t;
double weight;
...
public int compareTo(Edge o) {
if(this.getWeight() > o.getWeight())
return 1;
else if (.. == ..) return 0;
else return -1;
}
}
9.7 接口的组合
利用接口的多继承实现,本质是CRP原则。
public interface Ducklike extends Flyable, Quackable {}
public class Duck implements Ducklike {}
Java中很多原生库都是这样构建的
如Throwable、Comparable、Iteratable