定义
适配器模式(Adapter Pattern)属于结构型设计模式。目的是要使接口不兼容的对象能够相互兼容,比如客户端发送的消息有很多类型,如下图的ABC三种类型,发给系统内部去处理,那么如果有10000种类型,系统的负担就会很重。
适配器模式就是我们需要在客户端和系统中间加入一个“中间层”,这个适配器用来将ABC等类型的数据进行转换和统一,以一种标准通用的类型交给系统去处理,从而解决多类型导致系统沉重的负担。
特点
从适配器的设计来看,加入这个“中间层”解开了客户端和系统的强耦合,以一个适配器去转换为一类标准再去处理,从而解决了类型不适配的问题,具有良好的扩展性和可维护性。
举例说明
现实中,使用macBook的用户一定不陌生,macBook的接口只有typeC类型的雷电接口,我们如果要外接屏幕需要用到HDMI接口,要拷贝U盘数据需要用到USB接口,导入导出单反相机中的照片需要用到SD/TF卡槽等等,这些接口都与typeC接口不适配。
所以出现了一种“适配器”叫做扩展坞,这种扩展坞以多种类型的接口暴露给客户端,再以typeC接口与系统接口通信,从而达到多接口兼容的目的,这是现实中常见的适配器模式的案例。
代码实现
这里以类为基础的面向对象方式实现这种思想,使用TypeScript语言。首先我们知道要实现适配器模式肯定离不开客户端、系统、适配器这三个对象,于是这三个对象可以设计成三个类。IDock是适配器接口用来约束规范,客户端和适配器都要遵循这种规范。定义一个Adapter适配器类,里边需要封装一些转换的逻辑作为客户端与系统通信的“桥梁”。SystemService类是系统服务类,Client无法直接调用,而是要通过适配器间接调用,UML图如下。
这是扩展坞实现了接口供客户端去调用,适配器中经过处理再与系统服务交互。Adapter类中的method方法是主流程逻辑,translateData是数据转换逻辑;SystemService类中只需要关注内部的信息处理逻辑就好;Client定义了send方法与适配器进行交互。代码设计如下。
// 扩展坞接口 interface IDock { method(data: string): void; } // 适配器类 class Adapter implements IDock { private service: SystemService; constructor(service: SystemService) { this.service = service; } // 然后再调用系统服务类的方法进行分析 public method(data: string): void { console.log('适配器开始转换系统能够识别的数据'); const commonData = this.translateData(data); // 拿到通用数据后 调用系统服务类去处理信息 this.service.systemMain(commonData); } // 提供一个私有的转换方法 将数据转换为系统能够识别的数据 private translateData(data: string): string { return `系统通用的信息{${data}}`; } } // 系统服务类 提供了系统对信息处理的方法 class SystemService { public systemMain(data: string) :void{ console.log(`执行来自系统的方法,去处理:{${data}}`); } } class Client { private adapter: IDock; constructor(adapter: IDock) { this.adapter = adapter; } send(data: string) { this.adapter.method(data); } } // 创建一个系统服务实例 const systemService = new SystemService(); // 创建适配器实例 const adapter = new Adapter(systemService); // 创建一个客户端实例 const clt = new Client(adapter); // 调用send方法 传递来自USB的数据 clt.send('来自USB的数据'); // 调用send方法 传递来自HDMI的数据 clt.send('来自HDMI的数据');
客户端使用时先创建一个系统服务对象实例,再创建适配器对象实例将系统服务对象实例传入,保证了适配器是要与系统服务类中的systemMain方法进行交互。再创建一个客户端实例对象,将适配器实例对象作为参数,保证了客户端调用了这个适配器实例对象的method方法。这里写了两个send,客户端实例对象send之后,执行结果如下。
客户端实例对象调用send之后,执行了适配器对象中的method方法执行了适配器的主流程,method方法又调用了translateData方法将不同类型转换成了系统能够处理的标准通用类型,最终调用了系统服务对象的内部执行方法systemMain。
前端中的应用
除了基于类面向对象编程之外,前端日常应用中大量使用的是函数式编程,将适配器的思想融入,基本上就是数据结构不同时,为了不修改之前的逻辑,会定义一个转换方法作为中介处理完数据之后再连接之前的逻辑。例子如下。
const obj = { name: 'abc', age: 23 }; send(obj);
比如send方法将obj对象传给后端。如果现在后端数据结构改了,要在外边包一层data,传入当前时间戳stamp,并且使用json字符串的形式传输,为了不影响obj的代码结构,我们通常会写一个formatter方法,这也是日常函数式编程对适配器模式的思想应用。
const obj = { name: 'abc', age: 23 }; const formatter = (o) => { const now = new Date().valueOf(); return JSON.stringify({ data: { o }, stamp: now }) }; send(formatter(obj));
总结
适配器模式可谓是经常用到,并且作为一种重要设计思想,能够提高代码的兼容性、扩展性和可维护性。前端必须掌握的设计模式系列持续更新,如果对您有帮助希望多多点赞哦!