在OO框架中有时会使用Call Super,味道比较怪。有时想在框架里加点什么,会继承一个基类。常规说法:“要做自己的事情,只需在子类中实例化过程方法,但重要的要记住在自己方法开始时调用父类。”
看这个例子:
public class EventHandler …
public void handle (BankingEvent e) {
housekeeping(e);
}
public class TransferEventHandler extends EventHandler…
public void handle(BankingEvent e) {
super.handle(e);
initiateTransfer(e);
}
TransferEventHandler从EventHandler继承,并做了自己的初始化工作。不过在这里比较苦恼的事情就是你要记住super.handle()。感觉极差。象这种苦力活最好还是交给API本身来完成。
(以前写前台程序,尤其是控件事件的触发这样的代码时,往往会调用父类的事件)
通常可以采用Template Method来解决:
public class EventHandler …
public void handle (BankingEvent e) {
housekeeping(e);
doHandle(e);
}
protected void doHandle(BankingEvent e) {
}
public class TransferEventHandler extends EventHandler …
protected void doHandle(BankingEvent e) {
initiateTransfer(e);
}
这里handle是公共接口对外不变,父类中又提供了doHandle的hook方法,子类可以随意改写。这样写子类的轻松不少,父类的作者也可以随心所欲做他的housekeeping了。
handle/doHandle是一种不错的命名方法,比较清晰。
(template method我很喜欢,写MulProcBase就用了)
有些语言中可以对方法进行冻结,阻止被子类改写。从EnablingAttitide的角度出发,并不倾向于这样做,尤其对于继承的公开的接口。赞同冻结的意见往往是这样不会对父类造成破坏。我不觉得改写一个错误的方法也是一种“破坏”。子类、父类本就息息相关,要么都干活要么都不行。
不过在非冻结时,子类有可能会有特殊的操作,其行为无法预期。这时往往应该提供给子类前进的自由,但都让它走到你所需的终点,而不是简单地说“NO”。
(很难说哪一种态度更好,终极目标也许该是EnablingAttitude,但在我们目前的工程情况中,似乎怎能放手。
google和microsoft在软件的开发和使用是否也是两种态度,呵呵。
在用人的角度上来说,同样也会有两种态度,EnablingAttitude也是一种不错的尝试。给人以信心,给人以空间。)
Multi-Level Hooks
看一个JUnit的例子(Junit使用模板方法来控制全部的测试案例):
public abstract class TestCase
public void runBare() throws Throwable {
setUp();
try {
runTest();
}
finally {
tearDown();
}
}
protected void setUp() throws Exception {
}
protected void tearDown() throws Exception {
}
这是常见的模板方法,有两个空的hook method需要被改写。到目前为止一切都很好,你可以很方便地根据需要增加自己的setUp和tearDown代码。
不过当用户想从来自JUnit的另一个框架继承来使用时,有点麻烦了:
public class AlphaTestCase extends TestCase
protected void setUp() throws Exception {
alphaProjectSetup();
}
这里我们碰到了前面所说的call super问题,采用前面的建议重新定义:
public class AlphaTestCase extends TestCase…
final protected void setUp() throws Exception {
alphaProjectSetup();
doSetUp();
}
protected void doSetUp() throws Exception {
}
(Good! hehe)
不过当采用这个框架时,对于一个熟悉JUnit的用户来说就遇到了问题。每一个项目,每一本书籍,都告诉他需要改写setUp,而不是新发明的doSetUp。可能使用 final会比较好,但即使用了final,一个不同的setUp方法带来的混乱还是很痛苦的。
另一种选择。在第二层的框架改写调用setUp的地方。
public class AlphaTestCase extends TestCase…
public void runBare() throws Throwable {
alphaProjectSetup();
setUp();
try {
runTest();
}
finally {
tearDown();
}
}
protected void setUp() throws Exception {
}
现在大家还是可以象以前一样使用setUp。如果框架的作者想加入其他的一些行为,也没问题。
值得考虑的方法,如果一个模板方法不能合适的工作, 考虑往上移一层。(提供了一点思路,别一脑子闷死)
不是所有的模板都能让你那样自由。有些情况可能没有源代码,也无法确定做什么。框架的作者是一种DirectingAttitude,冻结了调用方法,阻止你改写。而且即使你能做,也要注意很多事。框架的作者有可能修改你已经改写的方法,方便的同时也带来了一种责任,你必须自己密切注释父类发生 的变化。
(双刃剑,使用的时候我希望框架是对的,不要我去操心,而想扩展功能时,又希望能让我看看源代码,做点改动什么的)
另一种方法目前可以采用的是使用
Annotation。Junit和其他基于Java的框架在近几个月已经可以在annotations工作,紧跟NUnit。Annotations允许你使用更多的元数据,而不仅仅是名字,允许你更多的选择。但那已经是另一篇文章的事了。
(这个我看不懂,下次再学习)
中文是我胡乱翻译的,括弧里是当时的胡思乱想,还是看
原文的好。