文章目录
- 1、适配器模式基本介绍
- 2、场景推导
- 3、Jdk中的适配器模式
1、适配器模式基本介绍
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁,将一个类的接口转换成客户希望的另一个接口, 使得原本由于接口不兼容不能在一起工作的类,可以在一起工作
2、场景推导
需求:计算两数之和
我们这样实现
class Calc { public int add(int a, int b) { return a + b; }}public class AdapterApp { public static void main(String[] args) { Calc ca = new Calc(); int r = ca.add(1, 2); System.out.println(r);//3 }}
现在变化来了,客户想要计算三个数的和,怎么办呢
好,有小伙伴说,那还不简单,我传入参数的时候再调一次add方法不就行了
class Calc { public int add(int a, int b) { return a + b; }}public class AdapterApp { public static void main(String[] args) { Calc ca = new Calc(); int r = ca.add(1, ca.add(2,3)); System.out.println(r);//6 }}
那你要这样玩的话,就没得聊了
言归正传,我们考虑用适配器模式解决
class Calc { public int add(int a, int b) { return a + b; }}//适配器class CalcAdapter { // 注意组合优于继承 private final Calc calc; public CalcAdapter(Calc calc) { this.calc = calc; } public int add(int x, int y, int z) { return calc.add(calc.add(x,y), z); }}------------------------------------------------------------------------------public class AdapterApp { public static void main(String[] args) { CalcAdapter ca = new CalcAdapter(new Calc()); int r = ca.add(1, 2, 3); System.out.println(r);//6 }}
我们看一下适配器模式做了什么事,以前add()方法只能传两个参数
但是用户的需求变了,用户想传三个参数,适配器做到了适配,后面4个参数,5个参数…都可以扩展
好了,是不是感觉适配器模式很简单呢
接下来看看在生产中设计模式的运用
3、Jdk中的适配器模式
在java线程里面的Runnable和Callable两个接口是通过适配转换的
写一段代码:
public class ThreadTest { // 线程池,线程任务提交给线程池执行 public static ExecutorService executorService = Executors.newFixedThreadPool(10); public static void main(String[] args) throws ExecutionException, InterruptedException { // 线程池运行Runnable方式 FutureTask> futureTask01 = new FutureTask<>(new Runnable() { @Override @SneakyThrows public void run() { System.out.println("线程池运行Runnable方式"); Thread.sleep(3000); } }, String.class); executorService.submit(futureTask01); // 线程池运行Callable方式 FutureTask futureTask02 = new FutureTask<>(new Callable () { @Override public String call() throws Exception { Thread.sleep(3000); System.out.println("线程池运行Callable方式"); // 返回一句话 return "线程池运行Callable方式返回:" + Thread.currentThread().getName(); } }); executorService.submit(futureTask02); System.out.println(futureTask02.get()); }}
可以看到创建了两种不同类型的任务:
- 一种是基于Runnable接口,是无返回值的任务;
- 一种基于Callable接口,是有返回值类型的任务
但是,居然都可以通过executorService#submit()方法提交到线程池中运行?难道这个方法是一个重载方法吗?
其实我们点进源码里面进行参看,发现是同一个方法
看来是FutureTask这个类搞得鬼,接下来我们看FutureTask类的两个构造器
// 使用 Callable 进行初始化public FutureTask(Callablecallable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; // ensure visibility of callable}// 使用 Runnable 初始化,并传入 result 作为返回结果。// Runnable 是没有返回值的,所以 result 一般没有用,置为 null 就好了public FutureTask(Runnable runnable, V result) { this.callable = Executors.callable(runnable, result); this.state = NEW; // ensure visibility of callable}
看这一行代码Executors.callable(runnable, result),明明写的是Executors#callable,但是传进去的确是一个Runnable,我们进去看看
public staticCallable callable(Runnable task, T result) { if (task == null) throw new NullPointerException(); // 在这里进行了适配,将Runnable -> Callable return new RunnableAdapter (task, result);}
//RunnableAdapter类static final class RunnableAdapterimplements Callable { final Runnable task; final T result; RunnableAdapter(Runnable task, T result) { this.task = task; this.result = result; } public T call() { task.run(); return result; }}
发现没有这个类,长得像什么,像适配器,首先它实现了Callable接口,又组合了Runnable接口,并且通过写了一个和Callable同名的call方法将Runnable#run
- 这是非常典型的适配模型,想要把 Runnable 适配成 Callable,首先要实现 Callable 的接口,接着在 Callable 的 call 方法里面调用被适配对象(Runnable)的方法
- 当然也可以将Callable适配成Runnable类,那为什么不呢?因为Callable可以提供更多的方法,例如获取返回值
可能有同学觉得,好像还是很简单,我只要记住适配器类继承要适配对象的抽象类,并组合适配对象就行了,这也不难嘛
确实关键点就是这两步,但是这种解耦、开闭的思想我们一定要融入平时的编码中