java设计模式之:适配器模式

news/2024/11/28 18:42:49/

文章目录

  • 适配器模式定义
  • 通用代码实现
  • 适用场景
  • 案例场景分析
    • 一坨坨代码实现
    • 适配器模式重构
  • 总结


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

说人话:这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。比如现实生活中的例子, 就像我们提到的万能充、数据线、MAC笔记本的转换头、出国旅游买个插座等等,他们都是为了适配各种不同的口 ,做的兼容。

适配器模式定义

image.png

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就像上⾯提到的可能是⼀些注册开户消息、商品下单消息等等。

⽽适配器的思想⽅式也恰恰可以运⽤到这⾥,并且我想强调⼀下,适配器不只是可以适配接⼝往往还可以适配⼀些属性信息。

92e2d638162facefd7d360311762a81.png

一坨坨代码实现

这⾥模拟了三个不同类型的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、灵活性和扩展性都非常好在不修改原有代码的基础上增加新的适配器类,符合“开闭原则”。


http://www.ppmy.cn/news/259524.html

相关文章

无盘服务器大内存的好处,4GB+64GB够用?看完秒懂,原来大内存还有那么多好处...

原标题&#xff1a;4GB64GB够用&#xff1f;看完秒懂&#xff0c;原来大内存还有那么多好处 随着智能手机的普及&#xff0c;手机APP也随之而来&#xff0c;成了我们生活的一部分。但是&#xff0c;随着APP数量的增多和体量的增大&#xff0c;也占据了手机的大部分内存&#xf…

4g运行内存手机还能用多久_现在手机搭载4G运行内存够用吗?

现在这个时代&#xff0c;普遍的人都认为&#xff0c;手机运行内存都是越大越好&#xff0c;也不管三七二十一。但实质上真的就是如此么&#xff1f;其实按照我们平时正常使用各类应用上的话的呢&#xff0c;是完全没有问题。但是大家为什么都更趋势于使用或者去购买大内存呢&a…

python需要电脑多大内存合适_电脑内存多大合适?对于不同的人内存多大才够用?...

选购电脑时&#xff0c;你或许会因为囊中羞涩对内存的选购上斟酌万分&#xff0c;今天装机之家小编就告诉你&#xff0c;作为不同的人&#xff0c;我们到底需要多大的内存才够用而又不浪费。 关于内存&#xff0c;不同的用户群都会有各自纠结的问题。想当年&#xff0c;xp盛行的…

32位Windows7上8G内存使用感受

原文地址为&#xff1a; 32位Windows7上8G内存使用感受 为什么要使用8G内存&#xff1f;在国内外各大论坛上&#xff0c;这都是一个有争议的问题。问题的反方论据非常充分&#xff1a; 除了少数专业领域&#xff0c;大多数应用程序不会需要超过1G的内存。 游戏使用的内存最多…

装linux电脑内存只有4G,linux下可用内存只有不到4G

硬件没报错&#xff0c;free -m查出的总内存还不到4G&#xff0c;最后居然是升级kernel时&#xff0c;没有升级成kernel-pae版本&#xff0c;而是标准的版本&#xff0c;所以内核可控制的总内存就有限&#xff0c;呵呵。。 操作系统在32bit x86平台上最大寻址空间只有4GB&#…

32位程序使用超过4G的内存

众所周知&#xff0c;所有的32位应用程序都有4GB的进程地址空间&#xff0c;因为32位地址最多可以映射4GB的内存。对于Microsoft Windows操作系统&#xff0c;应用程序可以访问2GB的进程地址空间&#xff08;32位Linux可以访问3GB地址空间&#xff09;&#xff0c;这就是称为用…

运行内存4g电脑装linux够用,win10系统4g内存够用吗|win10安装4g内存够不够用

win10系统用4G内存够不够&#xff1f;硬件的性能更新换代特别快&#xff0c;现在运行内存大部分都是4G及以上&#xff0c;可以说4G内存已经是最低标配了。win10系统是微软发行的最新版操作系统&#xff0c;我们知道操作系统对硬件是有一定要求的&#xff0c;尤其是运行内存&…

计算机内存多大够用,内存多大才够用?电脑内存选购指南

内存多大才够用?相信不少网友在使用电脑时都曾遇到过“运行卡顿”、“程序加载时间长”等情况,而这些一般都与内存有关,只要升级一下内存条便可轻松解决了!那么,内存多大才够用呢?针对这一问题,小编今天带来了电脑内存选购指南。 内存多大才够用?电脑内存选购指南 电脑…