(via: http://dreamhead.blogbus.com/index_4.html)
缘起
在Java里,为了启动一个线程,我们要创建一个实现Runnable接口的类:
public class MyParallelComputer implements Runnable {
pulbic void run() {
...
}
}
new Thread(new MyRunner()).start();
再有,为了响应一个按钮的事件,我们要创建一个ActionListener接口的类:
button.add(new ActionListener() {
public void actionPerformed(ActionEvent e) {
...
}
});
所有这些,其实,我们需要的并不是一个类,而是满足一定约定的调用接口,只是因为我们用的是Java,让我们别无选择。做同样的事情,对比其它程序设计语言:
- 很多程序设计语言里有lambda,甚至连方法名都可以省掉
- C#里有delegate,只要方法的入参和返回值兼容,方法名也不重要
- 即便是C,用函数指针,也只限定了入参和返回值,方法名也不重要
对比来看,Java的实现有两点堪称多余,类型和方法名。
在面向对象设计中,继承一种非常强的关系。虽然在语法层面上,我们只是简单地extends或implements一个类型,但实际上,背后隐藏着好多概念,比如is-a,比如LSP,所以,继承关系必须让我们小心翼翼,确保继承真的是继承。
再来看方法名,从之前几种程序设计语言的对比来看,对于这个调用接口而言,我们真正关心的只是入参和返回值,而方法名并不是我们的重点,而且,在现代程序设计中,方法名扮演着文档的作用,而实现这样的调用接口,我们不得不遵循这个死板的名字。所以,现实中经常的做法是,有一个按照自己意图命名的方法,在这个死板的方法中调用。
好吧,说了Java实现诸多的问题,接下来自然是要解决这样的问题,一起看看我们的主角:delegatej。
起步走
delegatej是一个基于annotation实现的delegate。从前面的介绍里面,你已经知道delegatej要解决问题是什么了。下面我们就来看看,如何应用delegatej。
假设我们有一个要实现的接口
public interface Executable {
String execute(String name);
}
既然是一个基于annotation的实现,我们自然要有一个annotation,它就是给方法做标记的:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Handle {
}
再来,我们有一个要实现Executable接口的方法:
public class Runner {
@Handle
public String hello(String name) {
return "hello " + name;
}
}
与实现接口不同的是,这里只是用annotation标记了方法,没有强硬的继承关系,方法名也与接口标记的完全不同,相同的只是入参和返回值而已。要把这样的方法对接到接口上,就是delegatej发挥作用的地方了:
Executable executable = delegate(Handle.class).to(Executable.class).trait(new Runner());
executable.execute("dreamhead"); // "hello dreamhead"
这段代码的意思很清楚,把Handle这个annotation标记的方法委托给(delegate to)Executable接口的实现,然后,用这个约定从一个对象(new Runner())压榨(trait)出对应的方法来。这样一来,我们就得到了一个Executable接口的实现,也就可以当做Executable来用了,执行这个方法时,就会调用到hello方法。
同样,那个启动线程的例子就可以稍微修改一下:
public class MyParallelComputer {
@Parallel
pulbic void compute() {
...
}
}
Runnable runnable = delegate(Parallel.class).to(Runnable.class).trait(new MyParallelComputer());
new Thread(runnable).start();
至于那个响应按钮事件的例子,就留给你做练习了。