您现在的位置是:首页 >技术教程 >设计模式之适配器模式网站首页技术教程

设计模式之适配器模式

King Gigi. 2024-06-17 10:13:52
简介设计模式之适配器模式

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线程里面的RunnableCallable两个接口是通过适配转换的

写一段代码:

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()方法提交到线程池中运行?难道这个方法是一个重载方法吗?

其实我们点进源码里面进行参看,发现是同一个方法

image-20221005143459209

看来是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可以提供更多的方法,例如获取返回值

可能有同学觉得,好像还是很简单,我只要记住适配器类继承要适配对象的抽象类,并组合适配对象就行了,这也不难嘛

确实关键点就是这两步,但是这种解耦、开闭的思想我们一定要融入平时的编码中

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。