若干客户使用class 接口中的同一子集;或者,两个classes 的接口有部分相同。
将相同的子集提炼到一个独立接口中。
动机(Motivation)
classes 之间彼此互用的方式有若干种。
「使用一个class 」通常意味覆盖该class 的所有责任区( whole area of responsibilities )。
另一种情况是,某一组客户只使用class 责任区中的一个特定子集。再一种情况则是,class 需要与「所有可协助处理某些特定请求」的classes 合作。
对于后两种情况,将「被使用之部分责任」分离出来通常很有意义,因为这样可以使系统的用法更清晰,同时也更容易看清系统的责任划分。如果新的需要支持上述子集,也比较能够看清子集内有些什么东西。
在许多面向对象语言中,这种「责任划分」能力是通过多重继承(multiple inheritance)支持的。你可以针对一段行为(each segment of behavior )建立一个class ,再将它们组合于一份实现品(implementation)中。
Java 只提供单一继承(single inher),但你可以运用interfaces (接口〉来昭示并实现上述需求。
interfaces 对于Java 程序的设计方式有着巨大的影响,就连Smalltalk 程序员都认为interfaces (接口) 是一大进步!
Extract Superclass 和Extract Interface 之间有些相似之处。
Extract Interface只能提炼共通接口,不能提炼共通代码。
使用Extract Interface 可能造成难闻的「重复」臭味,幸而你可以运用Extract Class 先把共通行为放进一个组件(component)中,然后将工作委托(delegating)该组件,
从而解决这个问题。如果有不少共通行为,Extract Superclass 会比较简单,但是每个class 只能有一个superclass (译注:每个class 却能有多个interfaces )。
如果某个class 在不同环境下扮演截然不同的角色,使用interface (接口)就是个好主意。
你可以针对每个角色以Extract Interface 提炼出相应接口。
另一种可以用上Extract Interface 的情况是:你想要描述一个class 的外驶接口(outbound interface ),亦即这个class 对其server 所进行的操作〉。如果你打算将来加入其他种类的server ,只需要求它们实现这个接口即可。
作法(Mechanics)
· | 新建一个空接口(empty interface )。 |
· | 在接口中声明「待提炼类」的共通操作。 |
· | 让相关的胡实现上述接口。 |
· | 调整客户端的型别声明,使得以运用该接口。 |
范例:(Example)
TimeSheet class 表示「月报表」,其中将计算花在员工身上的费用。为了计算这笔费用,TimeSheet 需要知道员工级别,以及该员工是否有特殊技能:
double charge(Employee emp, int days) {
int base = emp.getRate() * days;
if (emp.hasSpecialSkill())
return base * 1.05;
else return base;
}
除了提供员工的索费级别和特殊技能信息外,Employee 还有很多其他方面的功能,但本应用程序只需这两项功能。我可以针对这两项功能定义一个接口,从而强调「我 只需要这部分功能」的事实:
interface Billable {
public int getRate();
public boolean hasSpecialSkill();
}
然后,我声明Employee 实现这个接口 :
class Employee implements Billable ...
完成以后,我可以修改charge() 函数声明,强调该函数只使用Employee 的这部分行为:
double charge(Billable emp, int days) {
int base = emp.getRate() * days;
if (emp.hasSpecialSkill())
return base * 1.05;
else return base;
}
此刻,我们只不过是在文档化(documentability)方面获得了一些适度收获。对函 数,这样的收获并没有太大价值;但如果有若干classes 都使用Billable 接口,它就会很有用。如果我还想计算计算器租金,巨大的收获就显露出来了。为了让公司里的计算器都「能够被计费」(billable),我只需让Computer class 实现Billable 接口,然后就可以把计算器租金登记到月报表上了。