某个subclass 只使用superclass 接口中的一部分,或是根本不需要继承而来的数据。
在subclass 中新建一个值域用以保存superclass ;调整subclass 函数,令它改而委托superclass ;然后去掉两者之间的继承关系。
动机(Motivation)
继承(Inheritance )是一件很棒的事,但有时候它并不是你要的。常常你会遇到这样的情况:一开始你继承了一个class ,随后发现superclass 的许多操作并不真正 适用于subclass 。这种情况下你所拥有的接口并未真正反映出class 的功能。或者,你可能发现你从superclass 中继承了 一大堆subclass 并不需要的数据,抑或者你可能发现superclass 中的某些protected 函数对subclass 并没有什么意义。
你可以选择容忍,并接受传统说法:subclass 可以只使用superclass 功能的一部分。但这样做的结果是:代码传达的信息与你的意图南辑北辙——这是一种裩淆,你应该将它去除。
如果以委托(delegation)取代继承(Inheritance ),你可以更清楚地表明:你只需要受托类(delegated class)的一部分功能。接口中的哪一部分应该被使用,哪一部分应该被忽略,完全由你主导控制。这样做的成本则是需要额外写出请托函数(delegating methods),但这些函数都非常简单,极少可能出错。
作法(Mechanics)
· | 在subclass 中新建一个值域,使其引用(指向、指涉、refers)superclass 的一个实体,并将它初始化为this。 |
· | 修改subclass 内的每一个(可能)函数,让它们不再使用superclass ,转而使 用上述那个「受托值域」(delegated field)。每次修改后,编译并测试。 |
Ø | 你不能如此这般地修改subclass 中「通过super 调用superclass 函数」的函数,否则它们会陷入无限递归(infinite recurse)。这一类函数只有在继承关系被打破后才能修改。 |
· | 去除两个classes 之间的继承关系,将上述「受托值域」(delegated field)的赋值动作修改为「赋予一个新对象」。 |
· | 针对客户端所用的每一个superclass 函数,为它添加一个简单的请托函数(delegating method)。 |
· | 编译,测试。 |
范例:(Example)
「滥用继承」的一个经典范例就是让Stack class 继承Vector class 。Java 1.1的utility library(java.util)恰好就是这样做的。(这些淘气的孩子啊!)不过,作为范例,我只给出一个比较简单的形式:
class MyStack extends Vector {
public void push(Object element) {
insertElementAt(element,0);
}
public Object pop() {
Object result = firstElement();
removeElementAt(0);
return result;
}
}
只要看看Mystack 用户,我就会发现,用户只要它做四件事:push()、pop()、size() 和 isEmpty() 。后两个函数是从Vector 继承来的。
我要把这里的继承关系改为委托关系。首先,我要在中新建一个值域,用以保存「受托之Vector 对象」。一开始我把这个值域初始化为this,这样在重构进行过程中,我就可以同时使用继承和委托:
private Vector _vector = this;
现在,我开始修改MyStack 的函数,让它们使用委托关系。首先从push() 开始:
public void push(Object element) {
_vector.insertElementAt(element,0);
}
此时我可以编译并测试,一切都将运转如常。现在轮到pop() :
public Object pop() {
Object result = _vector.firstElement();
_vector.removeElementAt(0);
return result;
}
修改完所有subclass 函数后,我可以打破与superclass 之间的联系了 :
class MyStack extends Vector
private Vector _vector = new Vector();
然后,对于Stack 客户端可能用到的每一个Vector 函数(译注:这些函数原本是 继承而来的),我都必须在中添加一个简单的请托函数(delegating method):
public int size() {
return _vector.size();
}
public boolean isEmpty() {
return _vector.isEmpty();
}
现在我可以编译并测试。如果我忘记加入某个请托函数,编译器会告诉我。