适配器

适配器模式是Adapter,也称Wrapper,是指如果一个接口需要B接口,但是待传入的对象却是A接口,怎么办?

我们举个例子。如果去美国,我们随身带的电器是无法直接使用的,因为美国的插座标准和中国不同,所以,我们需要一个适配器:

在程序设计中,适配器也是类似的。我们已经有一个类,实现了Callable接口:

现在,我们想通过一个线程去执行它:

  1. Callable<Long> callable = new Task(123450000L);
  2. Thread thread = new Thread(callable); // compile error!
  3. thread.start();

发现编译不过!因为Thread接收Runnable接口,但不接收Callable接口,肿么办?

一个办法是改写Task类,把实现的Callable改为Runnable,但这样做不好,因为Task很可能在其他地方作为Callable被引用,改写Task的接口,会导致其他正常工作的代码无法编译。

这个RunnableAdapter类就是Adapter,它接收一个Callable,输出一个Runnable。怎么实现这个RunnableAdapter呢?我们先看完整的代码:

  1. public class RunnableAdapter implements Runnable {
  2. // 引用待转换接口:
  3. private Callable<?> callable;
  4. public RunnableAdapter(Callable<?> callable) {
  5. this.callable = callable;
  6. // 实现指定接口:
  7. // 将指定接口调用委托给转换接口调用:
  8. try {
  9. callable.call();
  10. } catch (Exception e) {
  11. throw new RuntimeException(e);
  12. }
  13. }
  14. }

编写一个Adapter的步骤如下:

  • 实现目标接口,这里是Runnable
  • 在目标接口的实现方法内部,调用待转换接口的方法。 这样一来,Thread就可以接收这个RunnableAdapter,因为它实现了Runnable接口。Thread作为调用方,它会调用RunnableAdapterrun()方法,在这个run()方法内部,又调用了Callablecall()方法,相当于Thread通过一层转换,间接调用了Callablecall()方法。

适配器模式在Java标准库中有广泛应用。比如我们持有数据类型是String[],但是需要List接口时,可以用一个Adapter:

注意到List<T> Arrays.asList(T[])就相当于一个转换器,它可以把数组转换为List

我们再看一个例子:假设我们持有一个InputStream,希望调用readText(Reader)方法,但它的参数类型是Reader而不是InputStream,怎么办?

当然是使用适配器,把“变成”Reader

  1. InputStream input = Files.newInputStream(Paths.get("/path/to/file"));
  2. Reader reader = new InputStreamReader(input, "UTF-8");
  3. readText(reader);

InputStreamReader就是Java标准库提供的Adapter,它负责把一个InputStream适配为Reader。类似的还有OutputStreamWriter

直接使用InputStreamReader这个Adapter是不行的,因为它只能转换出Reader接口。事实上,要把InputStream转换为FileReader也不是不可能,但需要花费十倍以上的功夫。这时,面向抽象编程这一原则就体现出了威力:持有高层接口不但代码更灵活,而且把各种接口组合起来也更容易。一旦持有某个具体的子类类型,要想做一些改动就非常困难。

使用Adapter模式将Callable接口适配为Runnable

适配器 - 图1下载练习: (推荐使用IDE练习插件快速下载)

Adapter模式可以将一个A接口转换为B接口,使得新的对象符合B接口规范。

编写Adapter实际上就是编写一个实现了B接口,并且内部持有A接口的类:

  1. public BAdapter implements B {
  2. private A a;
  3. public BAdapter(A a) {
  4. this.a = a;
  5. }
  6. public void b() {
  7. a.a();
  8. }

在Adapter内部将B接口的调用“转换”为对A接口的调用。

只有A、B接口均为抽象接口时,才能非常简单地实现Adapter模式。