Talk about the adapter pattern

Interface not working? OK, I'll help you adapt

I. Overview

Adapter mode (Adapter) is one of the structural modes among the 23 design modes ; it is like the expansion dock that we need to use when the interface on our computer is not enough, and it plays the role of transfer. It can connect new functions with original functions, so that functions that cannot be used due to changes in requirements can be reused.

img01.pngOn the Mac shown above, there are only two typec interfaces. When we need to use USB, network cable, HDMI and other interfaces, this is not enough, so we need an expansion dock to increase the interface of the computer

img02.pngCloser to home, let's understand the roles in the adapter pattern: requester (client), target role (Target), source role (Adaptee), adapter role (Adapter) , these four roles are the key to ensure the operation of this design pattern.

  • client: The object that needs to use the adapter, does not need to care about the internal implementation of the adapter, and only connects to the target role.
  • Target: The target role, directly connected to the client, defines the functions that the client needs to use.
  • Adaptee: The object that needs to be adapted.
  • Adapter: The adapter is responsible for converting the source object and adapting it to the client.

2. Introductory case

There are two types of adapter patterns: object adapters and class adapters . In fact, the difference between the two methods is that the implementation in the adapter class, the class adapter inherits the class of the source object, and the object adapter refers to the class of the source object.

Of course, the two methods have their own advantages and disadvantages, let's talk about them separately;

Class adapter: Due to the inheritance mode, the original method of Adaptee can be rewritten in the adapter, making the adapter more flexible; but there are limitations, Java is a single inheritance mode, so the adapter class can only inherit Adaptee, and cannot additionally inherit other class, which also leads to the fact that the Target class can only be an interface.

Object adapter: This mode avoids the disadvantage of single inheritance, and passes the Adaptee class to the Adapter by reference, so that the Adaptee object itself and its subclass objects can be passed, which is more open than the class adapter; but it is also because This openness leads to the need to redefine Adaptee and add additional operations.

类适配器UML图 img03.png 对象适配器UML图 img04.png 下面,是结合上面电脑的场景,写的一个入门案例,分别是四个类:ClientAdapteeAdapterTarget,代表了适配器模式中的四种角色。

/**
 * @author 往事如风
 * @version 1.0
 * @date 2023/5/9 15:54
 * @description:源角色
 */
public class Adaptee {
    /**
     * 需要被适配的适配的功能
     * 以Mac笔记本的typec接口举例
     */
    public void typeC() {
        System.out.println("我只是一个typeC接口");
    }
}
/**
 * @author 往事如风
 * @version 1.0
 * @date 2023/5/9 15:57
 * @description:目标接口
 */
public interface Target {

    /**
     * 定义一个转接功能的入口
     */
    void socket();
}
/**
 * @author 往事如风
 * @version 1.0
 * @date 2023/5/9 16:00
 * @description:适配器
 */
public class Adapter extends Adaptee implements Target {

    /**
     * 实现适配功能
     * 以Mac的拓展坞为例,拓展更多的接口:usb、typc、网线插口...
     */
    @Override
    public void socket() {
        typeC();
        System.out.println("新增usb插口。。。");
        System.out.println("新增网线插口。。。");
        System.out.println("新增typec插口。。。");
    }
}
/**
 * @author 往事如风
 * @version 1.0
 * @date 2023/5/9 15:52
 * @description:请求者
 */
public class Client {

    public static void main(String[] args) {
        Target target = new Adapter();
        target.socket();
    }
}

这个案例比较简单,仅仅是一个入门的demo,也是类适配器模式的案例,采用继承模式。在对象适配器模式中,区别就是Adapter这个适配器类,采用的是组合模式,下面是对象适配器模式中Adapter的代码;

/**
 * @author 往事如风
 * @version 1.0
 * @date 2023/5/9 16:00
 * @description:适配器
 */
public class Adapter implements Target {

    private Adaptee adaptee;

    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    /**
     * 实现适配功能
     * 以Mac的拓展坞为例,拓展更多的接口:usb、typc、网线插口...
     */
    @Override
    public void socket() {
        adaptee.typeC();
        System.out.println("新增usb插口。。。");
        System.out.println("新增网线插口。。。");
        System.out.println("新增typec插口。。。");
    }
}

三、运用场景

其实适配器模式为何会存在,全靠“烂代码”的衬托。在初期的设计上,一代目没有考虑到后期的兼容性问题,只顾自己一时爽,那后期接手的人就会感觉到头疼,就会有“还不如重写这段代码的想法”。但是这部分代码往往都是经过N代人的充分测试,稳定性比较高,一时半会还不能对它下手。这时候我们的适配器模式就孕育而生,可以在不动用老代码的前提下,实现新逻辑,并且能做二次封装。这种场景,我在之前的系统重构中深有体会,不说了,都是泪。

当然还存在一种情况,可以对不同的外部数据进行统一输出。例如,写一个获取一些信息的接口,你对前端暴露的都是统一的返回字段,但是需要调用不同的外部api获取不同的信息,不同的api返回给你的字段都是不同的,比如企业工商信息、用户账户信息、用户津贴信息等等。下面我对这种场景具体分析下;

首先,我定义一个接口,接收用户id和数据类型两个参数,定义统一的输出字段。

/**
 * @author 往事如风
 * @version 1.0
 * @date 2023/5/10 11:03
 * @description
 */
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserInfoController {

    private final UserInfoTargetService userInfoTargetService;

    @PostMapping("/info")
    public Result<DataInfoVo> queryInfo(@RequestParam Integer userId, @RequestParam String type) {
        return Result.success(userInfoTargetService.queryData(userId, type));
    }
}

定义统一的输出的类DataInfoVo,这里定义的字段需要暴露给前端,具体业务意义跟前端商定。

/**
 * @author 往事如风
 * @version 1.0
 * @date 2023/5/10 14:40
 * @description
 */
@Data
public class DataInfoVo {
    /**
     * 名称
     */
    private String name;
    /**
     * 类型
     */
    private String type;
    /**
     * 预留字段:具体业务意义自行定义
     */
    private Object extInfo;
}

然后,定义Target接口(篇幅原因,这里不做展示),Adapter适配器类,这里采用的是对象适配器,由于单继承的限制,对象适配器也是最常用的适配器模式。

/**
 * @author 往事如风
 * @version 1.0
 * @date 2023/5/10 15:09
 * @description
 */
@Service
@RequiredArgsConstructor
public class UserInfoAdapter implements UserInfoTargetService {
    /**
     * 源数据类管理器
     */
    private final AdapteeManager adapteeManager;

    @Override
    public DataInfoVo queryData(Integer userId, String type) {
        // 根据类型,得到唯一的源数据类
        UserBaseAdaptee adaptee = adapteeManager.getAdaptee(type);
        if (Objects.nonNull(adaptee)) {
            Object data = adaptee.getData(userId, type);
            return adaptee.convert(data);
        }
        return null;
    }
}

AdapteeManagerA class is defined here to represent the management Adapteeclass, and a map is maintained internally to store the real Adapteeclass.

/**
 * @author 往事如风
 * @version 1.0
 * @date 2023/5/10 15:37
 * @description
 */
public class AdapteeManager {

    private Map<String, UserBaseAdaptee> baseAdapteeMap;

    public void setBaseAdapteeMap(List<UserBaseAdaptee> adaptees) {
        baseAdapteeMap = adaptees.stream()
                .collect(Collectors.toMap(handler -> AnnotationUtils.findAnnotation(handler.getClass(), Adapter.class).type(), v -> v, (v1, v2) -> v1));
    }

    public UserBaseAdaptee getAdaptee(String type) {
        return baseAdapteeMap.get(type);
    }
}

Finally, according to the data type, three Adaptee classes are defined: AllowanceServiceAdaptee(subsidy), BusinessServiceAdaptee(enterprise business), UserAccountServiceAdaptee(user account).

/**
 * @author 往事如风
 * @version 1.0
 * @date 2023/5/10 15:00
 * @description
 */
@Adapter(type = "JT")
public class AllowanceServiceAdaptee implements UserBaseAdaptee {

    @Override
    public Object getData(Integer userId, String type) {
        // 模拟调用外部api,查询津贴信息
        AllowanceVo allowanceVo = new AllowanceVo();
        allowanceVo.setAllowanceType("管理津贴");
        allowanceVo.setAllowanceAccount("xwqeretry2345676");
        allowanceVo.setAmount(new BigDecimal(20000));
        return allowanceVo;
    }

    @Override
    public DataInfoVo convert(Object data) {
        AllowanceVo preConvert = (AllowanceVo) data;
        DataInfoVo dataInfoVo = new DataInfoVo();
        dataInfoVo.setName(preConvert.getAllowanceAccount());
        dataInfoVo.setType(preConvert.getAllowanceType());
        dataInfoVo.setExtInfo(preConvert.getAmount());
        return dataInfoVo;
    }
}
/**
 * @author 往事如风
 * @version 1.0
 * @date 2023/5/10 15:00
 * @description
 */
@Adapter(type = "QY")
public class BusinessServiceAdaptee implements UserBaseAdaptee {

    @Override
    public Object getData(Integer userId, String type) {
        // 模拟调用外部api,查询企业工商信息
        BusinessVo businessVo = new BusinessVo();
        businessVo.setBusName("xxx科技有限公司");
        businessVo.setBusCode("q24243Je54sdfd99");
        businessVo.setBusType("中大型企业");
        return businessVo;
    }

    @Override
    public DataInfoVo convert(Object data) {
        BusinessVo preConvert = (BusinessVo) data;
        DataInfoVo dataInfoVo = new DataInfoVo();
        dataInfoVo.setName(preConvert.getBusName());
        dataInfoVo.setType(preConvert.getBusType());
        dataInfoVo.setExtInfo(preConvert.getBusCode());
        return dataInfoVo;
    }
}
/**
 * @author 往事如风
 * @version 1.0
 * @date 2023/5/10 15:00
 * @description
 */
@Adapter(type = "YH")
public class UserAccountServiceAdaptee implements UserBaseAdaptee {

    @Override
    public Object getData(Integer userId, String type) {
        // 模拟调用外部api,查询企业工商信息
        UserAccountVo userAccountVo = new UserAccountVo();
        userAccountVo.setAccountNo("afsdfd1243567");
        userAccountVo.setAccountType("银行卡");
        userAccountVo.setName("中国农业银行");
        return userAccountVo;
    }

    @Override
    public DataInfoVo convert(Object data) {
        UserAccountVo preConvert = (UserAccountVo) data;
        DataInfoVo dataInfoVo = new DataInfoVo();
        dataInfoVo.setName(preConvert.getName());
        dataInfoVo.setType(preConvert.getAccountType());
        dataInfoVo.setExtInfo(preConvert.getAccountNo());
        return dataInfoVo;
    }
}

All three classes implement an interface UserBaseAdapteethat defines a unified specification

/**
 * @author 往事如风
 * @version 1.0
 * @date 2023/5/10 15:03
 * @description
 */

public interface UserBaseAdaptee {
    /**
     * 获取数据
     * @param userId
     * @param type
     * @return
     */
    Object getData(Integer userId, String type);

    /**
     * 数据转化为统一的实体
     * @param data
     * @return
     */
    DataInfoVo convert(Object data);
}

Among these classes, the focus is on UserInfoAdapterthe adapter class. The operation here is to get the data returned from the outside through the source data class, and finally convert the different data into a unified field and return it.

Here I did not follow a fixed pattern, but made a slight change. Change the method of referencing the source data class in the adapter class to add the source data class to the map for temporary storage, and finally obtain the source data class through the type field of the front-end transmission, which is also a manifestation of the flexibility of the object adapter.

4. Application in source code

In the source code of JDK, there is a class under JUC FutureTask, and one of its construction methods is as follows:

public class FutureTask<V> implements RunnableFuture<V> {
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
    
	public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }
}

In one of the constructors, the callable is adapted through the method of the Executors class, wrapped and returned through an adapter class of RunnableAdapter

public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }
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;
        }
    }

In this way, no matter whether Runnable or Callable is passed in, the task can be adapted. Although it looks like the call method of Callable is called, actually the run method of Runnable is called internally, and the incoming return data is returned to the outside for use.

V. Summary

Adapter mode is actually a relatively easy-to-understand design mode, but for most beginners, it is easy to forget it immediately after reading it, which is caused by the lack of practical application. In fact, programming mainly examines our thinking mode, just like this adapter mode, and understanding its application scenarios is the most important. If you are given a business scenario and you can have a general design idea or solution in your mind, then you have already grasped the essence. As for the specific landing, it is inevitable to forget some details, and it will come back to mind immediately after looking through the information.

Finally, every time you encounter a problem, summarize it with your heart, and you will be one step closer to success.

6. Reference source code

编程文档:
https://gitee.com/cicadasmile/butte-java-note

应用仓库:
https://gitee.com/cicadasmile/butte-flyer-parent

おすすめ

転載: juejin.im/post/7233782463078907941