阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

适配器

29次阅读
没有评论

共计 2663 个字符,预计需要花费 7 分钟才能阅读完成。

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

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

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

适配器

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

public class Task implements Callable<Long> {private long num;
    public Task(long num) {this.num = num;
    }

    public Long call() throws Exception {long r = 0;
        for (long n = 1; n <= this.num; n++) {r = r + n;}
        System.out.println("Result:" + r);
        return r;
    }
}

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

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

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

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

另一个办法不用改写 Task 类,而是用一个 Adapter,把这个 Callable 接口“变成”Runnable接口,这样,就可以正常编译:

Callable<Long> callable = new Task(123450000L);
Thread thread = new Thread(new RunnableAdapter(callable));
thread.start();

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

public class RunnableAdapter implements Runnable {// 引用待转换接口:
    private Callable<?> callable;

    public RunnableAdapter(Callable<?> callable) {this.callable = callable;
    }

    // 实现指定接口:
    public void run() {// 将指定接口调用委托给转换接口调用:
        try {callable.call();
        } catch (Exception e) {throw new RuntimeException(e);
        }
    }
}

编写一个 Adapter 的步骤如下:

  1. 实现目标接口,这里是Runnable
  2. 内部持有一个待转换接口的引用,这里是通过字段持有 Callable 接口;
  3. 在目标接口的实现方法内部,调用待转换接口的方法。

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

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

String[] exist = new String[] {"Good", "morning", "Bob", "and", "Alice"};
Set<String> set = new HashSet<>(Arrays.asList(exist));

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

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

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

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

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

如果我们把 readText(Reader) 方法参数从 Reader 改为 FileReader,会有什么问题?这个时候,因为我们需要一个FileReader 类型,就必须把 InputStream 适配为FileReader

FileReader reader = new InputStreamReader(input, "UTF-8"); // compile error!

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

练习

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

下载练习

小结

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

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

public BAdapter implements B {private A a;
    public BAdapter(A a) {this.a = a;
    }
    public void b() {a.a();
    }
}

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

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

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计2663字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中