我正在参与「启航计划」

适配器形式(Adapter Pattern):将一个类的接口变换成客户端所等待的另一种接口,从而使原本因接口不匹配而无法在一同作业的两个类能够在一同作业。

说人话:这个形式便是用来做适配的,它将不兼容的接口转化为可兼容的接口,让原本由于接口不兼容而不能一同作业的类能够一同作业。比方现实生活中的比方, 就像咱们提到的万能充、数据线、MAC笔记本的转化头、出国旅行买个插座等等,他们都是为了适配各种不同的口 ,做的兼容。

适配器形式界说

如何在业务开发中使用适配器模式?

Target方针人物:该人物界说把其他类转化为何种接口, 也便是咱们的期望接口, 比方中的IUserInfo接口便是方针人物。

Adaptee源人物:你想把谁转化成方针人物, 这个“谁”便是源人物, 它是现已存在的、 运行良好的类或方针, 经过适配器人物的包装, 它会成为一个崭新、 靓丽的人物。

Adapter适配器人物:适配器形式的核心人物, 其他两个人物都是现已存在的人物, 而适配器人物是需求新建立的, 它的职责十分简单: 把源人物转化为方针人物, 怎么转化? 经过承继或是类相关的办法。

通用代码完成

/**
 * 方针人物
 */
public interface Target {
    void t1();
    void t2();
    void t3();
}
/**
 * 方针人物完成类
 */
public class ConcreteTarget implements Target{
    @Override
    public void t1() {
        System.out.println("方针人物 t1 办法");
    }
    @Override
    public void t2() {
        System.out.println("方针人物 t2 办法");
    }
    @Override
    public void t3() {
        System.out.println("方针人物 t3 办法");
    }
}
/**
 * 源人物:要把源人物转化成方针人物
 */
public class Adaptee {
    public void a1(){
        System.out.println("源人物 a1 办法");
    }
    public void a2(){
        System.out.println("源人物 a2 办法");
    }
    public void a3(){
        System.out.println("源人物 a3 办法");
    }
}

根据承继的类适配器

/**
 * 适配器人物
 */
public class Adapter extends Adaptee implements Target{
    @Override
    public void t1() {
        super.a1();
    }
    @Override
    public void t2() {
        super.a2();
    }
    @Override
    public void t3() {
        super.a3();
    }
}

根据组合的方针适配器

public class AdapterCompose implements Target{
    private Adaptee adaptee;
    public AdapterCompose(Adaptee adaptee){
        this.adaptee = adaptee;
    }
    @Override
    public void t1() {
        adaptee.a1();
    }
    @Override
    public void t2() {
        adaptee.a2();
    }
    @Override
    public void t3() {
        adaptee.a3();
    }
}

测试

public class AdapterClient {
    public static void main(String[] args) {
        // 原有的事务逻辑
        Target target = new ConcreteTarget();
        target.t1();
        // 根据承继 增加适配器事务逻辑
        Target target1 = new Adapter();
        target1.t1();
        // 根据组合 增加适配器事务逻辑
        Target target2 = new AdapterCompose(new Adaptee());
        target2.t1();
    }
}

打印成果:

方针人物 t1 办法
源人物 a1 办法
源人物 a1 办法

适配器形式有两种完成办法:类适配器和方针适配器。其间,类适配器运用承继联系来完成,方针适配器运用组合联系来完成。在实践开发中,挑选的依据如下:

1、假如 Adaptee 接口并不多,那两种完成办法都能够。

2、假如 Adaptee 接口许多,并且 Adaptee 和 ITarget 接口界说大部分都相同,那咱们推荐运用类适配器,由于 Adaptor 复用父类 Adaptee 的接口,比起方针适配器的完成办法,Adaptor 的代码量要少一些。

3、假如 Adaptee 接口许多,并且 Adaptee 和 ITarget 接口界说大部分都不相同,那咱们推荐运用方针适配器,由于组合结构相关于承继更加灵敏。

适用场景

1、修正已运用的接口,某个现已投产中的接口需求修正,这时分运用适配器最好。

2、统一多个类的接口设计,比方关于敏感词过滤,需求调用好几个第三方接口,每个接口办法名,办法参数又不相同,这时分运用适配器形式,将一切第三方的接口适配为统一的接口界说。

3、兼容老版别接口。

4、适配不同格局的数据。

事例场景分析

现在假设⼀个体系需求接纳各式各样的MQ音讯或许接⼝,假如⼀个个的去开发,就会消耗很⼤的本钱,同时关于后期的拓宽也有⼀定的难度。此刻就会希望有⼀个体系能够装备⼀下就把外部的MQ接⼊进⾏,这些MQ就像上⾯提到的可能是⼀些注册开户音讯、产品下单音讯等等。

⽽适配器的思想⽅式也恰恰能够运⽤到这⾥,并且我想着重⼀下,适配器不只是能够适配接⼝往往还能够适配⼀些特点信息。

如何在业务开发中使用适配器模式?

一坨坨代码完成

这⾥模仿了三个不同类型的MQ音讯,⽽在音讯体中都有⼀些必要的字段,⽐如;⽤户ID、时刻、事务ID,可是每个MQ的字段特点并不⼀样。就像⽤户ID在不同的MQ⾥也有不同的字段:uId、userId等。

注册开户MQ

public class CreateAccount {
    private String number;      // 开户编号
    private String address;     // 开户地
    private Date accountDate;   // 开户时刻
    private String desc;        // 开户描绘
 	// ... get/set
}

内部订单MQ

public class OrderMq {
    private String uid;           // 用户ID
    private String sku;           // 产品
    private String orderId;       // 订单ID
    private Date createOrderTime; // 下单时刻
 	// ... get/set
}

第三⽅订单MQ

public class POPOrderDelivered {
    private String uId;     // 用户ID
    private String orderId; // 订单号
    private Date orderTime; // 下单时刻
    private Date sku;       // 产品
    private Date skuName;   // 产品称号
    private BigDecimal decimal; // 金额
    // ... get/set
}

Mq接纳音讯完成

public class CreateAccountMqService {
    public void onMessage(String message) {
        CreateAccount mq = JSON.parseObject(message, CreateAccount.class);
        mq.getNumber();
        mq.getAccountDate();
        // ... 处理自己的事务
    }
}

三组MQ的音讯都是⼀样模仿使⽤,就不⼀⼀展现了。

适配器形式重构

统⼀的MQ音讯体

public class RebateInfo {
    private String userId;  // 用户ID
    private String bizId;   // 事务ID
    private Date bizTime;   // 事务时刻
    private String desc;    // 事务描绘
	// ... get/set
}

MQ音讯中会有多种多样的类型特点,尽管他们都有相同的值提供给使⽤⽅,可是假如都这样接⼊那么当MQ音讯特别多时分就会很费事。

所以在这个事例中咱们界说了通⽤的MQ音讯体,后续把一切接⼊进来的音讯进⾏统⼀的处理。

MQ音讯体适配类

public class MQAdapter {
    public static RebateInfo filter(String strJson, Map<String, String> link) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        return filter(JSON.parseObject(strJson, Map.class), link);
    }
    public static RebateInfo filter(Map obj, Map<String, String> link) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        RebateInfo rebateInfo = new RebateInfo();
        for (String key : link.keySet()) {
            Object val = obj.get(link.get(key));
            RebateInfo.class.getMethod("set" + key.substring(0, 1).toUpperCase() + key.substring(1), String.class).invoke(rebateInfo, val.toString());
        }
        return rebateInfo;
    }
}

首要⽤于把不同类型MQ种的各种特点,映射成咱们需求的特点并回来。就像⼀个特点中有 ⽤户ID;uId ,映射到咱们需求的 userId ,做统⼀处理。

⽽在这个处理过程中需求把映射办理传递给 Map<String, String> link ,也便是准确的描绘了,当时MQ中某个特点称号,映射为咱们的某个特点称号。

终究由于咱们接纳到的 mq 音讯根本都是 json 格局,能够转化为MAP结构。最后使⽤反射调⽤的⽅式给咱们的类型赋值

在实践事务开发中,除了反射的运用外,还能够加入代理类把映射的装备交给它。这样就能够不需求每一个mq都手动创立类了。

测试类

public class ApiTest {
    @Test
    public void test_MQAdapter() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ParseException {
        SimpleDateFormat s = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date parse = s.parse("2023-02-27 20:20:16");
        CreateAccount createAccount = new CreateAccount();
        createAccount.setNumber("1000");
        createAccount.setAddress("北京");
        createAccount.setAccountDate(parse);
        createAccount.setDesc("在校开户");
        HashMap<String, String> link01 = new HashMap<String, String>();
        link01.put("userId", "number");
        link01.put("bizId", "number");
        link01.put("bizTime", "accountDate");
        link01.put("desc", "desc");
        RebateInfo rebateInfo01 = MQAdapter.filter(createAccount.toString(), link01);
        System.out.println("mq.createAccount(适配前)" + createAccount.toString());
        System.out.println("mq.createAccount(适配后)" + JSON.toJSONString(rebateInfo01));
    }
}
mq.createAccount(适配前){"accountDate":1677500416000,"address":"北京","desc":"在校开户","number":"1000"}
mq.createAccount(适配后){"bizId":"1000","bizTime":1591077840669,"desc":"在校开户","userId":"1000"}

模仿传⼊不同的MQ音讯,并设置字段的映射联系。等真的事务场景开发中,就能够配这种映射装备联系交给装备⽂件或许数据库后台装备,削减编码。

总结

1、将方针类和适配者类解耦,经过运用适配器让不兼容的接口变成了兼容,让客户从完成的接口解耦。

2、增加了类的透明性和复用性,将具体的完成封装在适配者类中,关于客户端类来说是透明的,并且提高了适配者的复用性。

3、灵敏性和扩展性都十分好在不修正原有代码的基础上增加新的适配器类,符合“开闭原则”。