适配器模式(Adapter Pattern):将一个类的接口变换成客户端所期待的另一种接口,从而使本来因接口不匹配而无奈在一起工作的两个类可能在一起工作。
说人话:这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让本来因为接口不兼容而不能一起工作的类能够一起工作。比方现实生活中的例子, 就像咱们提到的万能充、数据线、MAC笔记本的转换头、出国游览买个插座等等,他们都是为了适配各种不同的口 ,做的兼容。
适配器模式定义
Target指标角色:该角色定义把其余类转换为何种接口, 也就是咱们的冀望接口, 例子中的IUserInfo接口就是指标角色。
Adaptee源角色:你想把谁转换成指标角色, 这个“谁”就是源角色, 它是曾经存在的、 运行良好的类或对象, 通过适配器角色的包装, 它会成为一个簇新、 靓丽的角色。
Adapter适配器角色:适配器模式的外围角色, 其余两个角色都是曾经存在的角色, 而适配器角色是须要新建设的, 它的职责非常简单: 把源角色转换为指标角色, 怎么转换? 通过继承或是类关联的形式。
通用代码实现
/**
- 指标角色
*/
public interface Target {
void t1();void t2();void t3();
}
复制代码
/**
- 指标角色实现类
*/
public class ConcreteTarget implements Target{
@Overridepublic void t1() { System.out.println("指标角色 t1 办法");}@Overridepublic void t2() { System.out.println("指标角色 t2 办法");}@Overridepublic 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{
@Overridepublic void t1() { super.a1();}@Overridepublic void t2() { super.a2();}@Overridepublic void t3() { super.a3();}
}
复制代码
基于组合的对象适配器
public class AdapterCompose implements Target{
private Adaptee adaptee;public AdapterCompose(Adaptee adaptee){ this.adaptee = adaptee;}@Overridepublic void t1() { adaptee.a1();}@Overridepublic void t2() { adaptee.a2();}@Overridepublic 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; // 用户IDprivate String sku; // 商品private String orderId; // 订单IDprivate Date createOrderTime; // 下单工夫 // ... get/set
}
复制代码
第三⽅订单MQ
public class POPOrderDelivered {
private String uId; // 用户IDprivate 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; // 用户IDprivate String bizId; // 业务IDprivate 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 {
@Testpublic 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、灵活性和扩展性都十分好在不批改原有代码的根底上减少新的适配器类,合乎“开闭准则”。