代码也写了几年了,前端设计模式处于看了忘,计模忘了看的式系状态,最近对设计模式有了点感觉,列适索性就再学习总结下吧。配器 大部分讲设计模式的模式文章都是使用的 Java、C++ 这样的前端以类为基础的静态类型语言,作为前端开发者,计模js 这门基于原型的式系动态语言,函数成为了一等公民,列适在实现一些设计模式上稍显不同,配器甚至简单到不像使用了设计模式,模式有时候也会产生些困惑。前端 下面按照「场景」-「设计模式定义」- 「代码实现」- 「更多场景」-「总」的计模顺序来总结一下,如有不当之处,式系欢迎交流讨论。 当我们使用第三方库的时候,常常会遇到当前接口和第三方接口不匹配的情况,比如使用一个 Table 的组件,它要求我们返回的表格数据格式如下: { code: 0, // 业务 code msg: , // 出错时候的提示 data: { total: , // 总数量 list: [], // 表格列表 } 但后端返回的数据可能是高防服务器这样的: { code: 0, // 业务 code message: , // 出错时候的提示 data: { total: , // 总数量 records: [], // 表格列表 } 此时就可以通过适配器模式进行转换。 看一下 维基百科 给的定义: ★In software engineering, the adapter pattern is a software design pattern that allows the interface of an existing class to be used as another interface.[1] It is often used to make existing classes work with others without modifying their source code.”通过适配器模式可以让当前 class 不改变的情况下正常使用另一个 class。 在以 class 为基础的语言中有两种实现方式,一种是通过组合的方式,适配器类内部包含原对象的实例。一种是通过类继承,适配器类继承原 class 。可以看下 UML 类图: 左边的 Adapter 内部拥有 Adaptee的实例,右边的 Adapter 类直接继承 Adaptee 类。 适配器会将 Adaptee 的 specificOperation 方法进行相应的处理包装为operation 方法供 client 使用。 看一个简单的例子,现实生活中iPhone 有两种耳机插口,一种是 Lightning,一种是传统的 3.5 毫米接口。如果是站群服务器 lightning 插口的耳机想要插到传统的 3.5 毫米接口的电脑上就需要适配器了。 class Lightning耳机 { public void 插入Lighting接口(){ System.out.println("插入到Lighting耳机接口成功"); } } class 传统耳机 { public void 插入到传统耳机孔(){ System.out.println("插入到传统耳机孔成功"); } } class Lightning耳机到传统耳机适配器 extends 传统耳机 { public Lightning耳机 Lightning耳机; public Lightning耳机到传统耳机适配器(Lightning耳机 耳机) { Lightning耳机 = 耳机; } public void 插入到传统耳机孔(){ Lightning耳机.插入Lighting接口(); } } class 电脑传统耳机孔 { public 传统耳机 耳机; public 电脑传统耳机孔(传统耳机 传统耳机) { 耳机 = 传统耳机; } public void 插入耳机() { 耳机.插入到传统耳机孔(); } } public class Main { public static void main(String[] args) { 传统耳机 传统耳机 = new 传统耳机(); 电脑传统耳机孔 电脑传统耳机孔 = new 电脑传统耳机孔(传统耳机); 电脑传统耳机孔.插入耳机(); // 插入到传统耳机孔成功 Lightning耳机 Lightning耳机 = new Lightning耳机(); 电脑传统耳机孔 电脑传统耳机孔2 = new 电脑传统耳机孔(new Lightning耳机到传统耳机适配器(Lightning耳机)); 电脑传统耳机孔2.插入耳机(); // 插入到Lighting耳机接口成功 } 通过适配器我们成功将 Lightning 耳机插入到了电脑传统耳机孔,让我们再用js 改写一下。 const Lightning耳机 = { 插入Lighting接口(){ console.log("插入到Lighting耳机接口成功"); } } const 传统耳机 = { 插入到传统耳机孔(){ console.log("插入到传统耳机孔成功"); } } const 电脑传统耳机孔 = { 插入耳机(耳机) { 耳机.插入到传统耳机孔(); } } const Lightning耳机到传统耳机适配器 = function(Lightning耳机) { return { 插入到传统耳机孔(){ Lightning耳机.插入Lighting接口() } } } 电脑传统耳机孔.插入耳机(传统耳机) // 插入到传统耳机孔成功 回到开头接口不匹配的问题上,Table组件提供了一个 responseProcessor的钩子,我们只需要通过这个钩子将接口返回的数据进行包装即可。 { ... responseProcessor(res) { return { ...res, msg: res.message, // 出错时候的提示 data: { ...res.data list: res?.data?.records || [], // 表格列表 } }; }, ... 除了应对数据格式不一致的问题,通过适配器模式我们还可以为上层提供统一接口,来解决兼容性问题。最典型的例子就是 jQuery ,可以看一下其中一段代码: // Create the request object // (This is still attached to ajaxSettings for backward compatibility) jQuery.ajaxSettings.xhr = window.ActiveXObject !== undefined ? // Support: IE6-IE8 function() { // XHR cannot access local files, always use ActiveX for that case if ( this.isLocal ) { return createActiveXHR(); } // Support: IE 9-11 // IE seems to error on cross-domain PATCH requests when ActiveX XHR // is used. In IE 9+ always use the native XHR. // Note: this condition wont catch Edge as it doesnt define // document.documentMode but it also doesnt support ActiveX so it wont // reach this code. if ( document.documentMode > 8 ) { return createStandardXHR(); } // Support: IE<9 // oldIE XHR does not support non-RFC2616 methods (#13240) // See http://msdn.microsoft.com/en-us/library/ie/ms536648(v=vs.85).aspx // and http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9 // Although this check for six methods instead of eight // since IE also does not support "trace" and "connect" return /^(get|post|head|put|delete|options)$/i.test( this.type ) && createStandardXHR() || createActiveXHR(); } : // For all other browsers, use the standard XMLHttpRequest object 适配器模式和代理模式在代码结构上很像,代理模式也是对原对象进行包装处理。区别在于它们的意图不同: 适配器模式是一种比较简单的设计模式,在 js 中也会很自然的应用,一般通过一个函数进行转换即可。场景
适配器模式
image-20220213124112500
代码实现
更多场景
易混设计模式
总