您现在的位置是:首页 >技术教程 >设计模式之适配器模式网站首页技术教程
设计模式之适配器模式
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<Class<?>> futureTask01 = new FutureTask<>(new Runnable() {
@Override
@SneakyThrows
public void run() {
System.out.println("线程池运行Runnable方式");
Thread.sleep(3000);
}
}, String.class);
executorService.submit(futureTask01);
// 线程池运行Callable方式
FutureTask<String> futureTask02 = new FutureTask<>(new Callable<String>() {
@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(Callable<V> callable) {
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 static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
// 在这里进行了适配,将Runnable -> Callable
return new RunnableAdapter<T>(task, result);
}
//RunnableAdapter类
static final class RunnableAdapter<T> implements Callable<T> {
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可以提供更多的方法,例如获取返回值
可能有同学觉得,好像还是很简单,我只要记住适配器类继承要适配对象的抽象类,并组合适配对象
就行了,这也不难嘛
确实关键点就是这两步,但是这种解耦、开闭的思想我们一定要融入平时的编码中