设计模式:适配器模式

设计模式是通用的、可复用的代码设计方案,也可以说是针对某类问题的解决方案,因此,掌握好设计模式,可以帮助我们编写更健壮的代码。

wiki中将设计模式分为四类,分别是:

  • 创建模式(creational patterns)
  • 结构模式(structural patterns)
  • 行为模式(behavioral patterns)
  • 并发模式(concurrency patterns)

适配器模式属于其中的结构型模式,结构型------从名称上就可以看出------与结构有关,应用这类模式不会影响对象的行为,但是会影响代码结构。那么适配器模式究竟是怎样的一种解决方案,适合什么场景呢,接下来我们就来探究一下。

适配

首先我们来先扣个字眼,适配是什么意思呢?我直接问ChatGPT,得到了以下的回答:

"适配"在计算机领域通常指的是使软件、程序或网站能够在不同的设备、平台或环境下正常工作和显示的过程。适配的主要目的是确保用户能够在各种设备上获得一致的用户体验,而不受设备类型、屏幕尺寸、操作系统或浏览器等因素的影响。

简单粗暴一点理解就是指的兼容性。

所以应用适配器模式的目的简单来理解,就是提高兼容性。

这类生活场景非常常见,比如公司给员工配了一台显示器,但是外接屏幕自带连接线的接头与电脑已有的接口不适配,两者无法连接上,这种情况是很常见的,随着技术升级、数码设备不断的升级换代,导致市面上存在很多类型的接口,于是转接头就应运而生了------转接头就是适配器模式的一种具象应用。

编程

适配在编程中也是一个常见需求。就比如WIKI中的描述:

It is often used to make existing classes work with others without modifying their source code.

翻译过来的意思是:它(适配器模式)通常用于在不修改源代码的情况下使现有类与其他类协同工作。

很多开发小伙伴在现实工作中对这点应该都有所体会,在程序员的工作中很多时候都需要去维护已有项目,迭代新的需求,然后就可能碰到这类场景。

比如老代码中有些类或者对象的某个方法,在项目中的其他地方有调用,但是某天,突然来了一个需求,增加一个功能模块,然后这个模块需要与这些类或者对象交互,实现与这个方法类似但存在细微不同点的功能。

针对这个需求,如果不应用适配器模式,我们可以有以下两种做法:

第一种,如果这个方法调用的地方比较少,这里是说如果,那我们可以简单粗暴地直接把这个方法改了,测试环节就稍微麻烦点,在测试新功能模块的同时还需要回归测试原本调用这个方法的地方。

第二种,给对象增加一个新的方法提供给新模块调用,当然这个新方法中的很多代码会与老方法中的代码重复。

很显然,这两种做法都不够好,第一种需要回归测试,而且很容易有遗漏,甚至可能引起原因不明的bug;第二种则可能使项目中存在很多冗余代码,而且还可能影响后期维护,比如修改某段逻辑就要修改两个方法的代码,如果是其他人来接手维护,很可能根本不知道是这样的情况。

适配器模式就可以应用于这类场景,它主要帮助我们解决以下问题:

  • 代码复用
  • 让接口不兼容的类协同工作

模式描述

适配器模式描述了它是如何帮助我们解决上述问题的:

  • 定义一个单独的适配器类,将一个类(待适配)的(不兼容)接口转换成客户端需要的另一个接口(target)。
  • 通过适配器来处理(重用)不具备所需接口的类。

也就是说,应用适配器模式主要做的事情,就是在代码中增加一个适配器的角色。

前端应用

JavaScript作为一种面向对象编程语言,当然也可以应用适配器模式。我们来看下面的一个例子:

Web端在以前使用Ajax技术处理异步的时候,都是通过XHR对象,但是随着Promise的推出,出现了更简洁的fetch方法,为了更方便地处理异步,现在某负责人准备在新项目中应用fetch方法,新项目从老项目中拷贝了基础文件,其中包括了Ajax代码,为了使项目成员快速熟悉,Ajax方法最好在用法上保持一致。

假设以下是原本的Ajax代码,是基于XMLHttpRequest对象进行封装的:

javascript 复制代码
function Ajax(method, url, {query, params, headers, successCallback }) {
    // 1. 创建对象
    const xhr = new XMLHttpRequest();
    // 2. 初始化 设置请求类型和url
    method = method.toUpperCase();
    let queryString = '?';
    if (query && query instanceof Object) {
        for (const key in query) {
            queryString += `${key}=${query[key]}&`
        }
        url += queryString.substring(0, queryString.length - 1);
    }
    xhr.open(method, url);
    // 3. 设置请求头
    for (const key in headers) {
        xhr.setRequestHeader(key, headers[key]);
    }
    // 4. 发送请求
    let paramString = '';
    if (params && params instanceof Object) {
        for (const key in params) {
            paramString += `${key}=${params[key]}&`
        }
        paramString = paramString.substring(0, paramString.length - 1);
    }
    xhr.send(paramString);
    // 5. 事件绑定 处理服务端返回的结果
    // on 当...的时候
    // readystate 0-初始化创建的时候 1-open的时候 2-send的时候 3-服务端部分返回的时候 4-服务端返回全部的时候
    // change 改变
    xhr.onreadystatechange = function () {
        // 判断 服务端返回了所有的结果
        if (xhr.readyState === 4) {
            // 判断响应状态码 200 404 403 401 500
            // 2xx 成功
            if (xhr.status >= 200 && xhr.status < 300) {
                let result = {
                    status: xhr.status, // 状态码
                    statusText: xhr.statusText, // 状态字符串
                    responseHeaders: xhr.getAllResponseHeaders(), // 所有响应头
                    response: xhr.response // 响应体
                }
                successCallback(result);
            }
        }
    }
}

为了使项目中熟悉XHR调用方式的成员和熟悉fetch的成员能统一调用Ajax方法,我们可以使用适配器模式来对代码进行改造。

首先创建一个适配器对象,在JavaScript中我们知道,函数也是对象,所以我们定义如下函数:

javascript 复制代码
async function AjaxAdapter(method, url, {query, params, headers, successCallback }) {
  // ...
}

这个函数的入参与原本的Ajax函数保持一致。在这里声明async异步函数是因为fetch方法的返回值是Promise类型。

然后我们修改Ajax函数,去调用这个适配器函数:

javascript 复制代码
async function Ajax(method, url, {query, params, headers, successCallback }) {
    return AjaxAdapter(method, url, {query, params, headers, successCallback });
}

我们在适配器函数中去处理不同的使用方式,比如如果调用者传递了successCallback这个回调函数,说明他是旧方式的使用者,那么就在适配器函数中将异步返回的结果通过successCallback进行传递,否则就不对异步结果做处理,由调用者自行处理异步函数的结果。

这样无论是XHR的使用者还是fetch的使用者,都能同样使用Ajax函数获取异步结果,而不会感知到其中的不同,XHR的使用者就不必一定要去学习fetch的使用,只要像以前一样使用Ajax函数即可。

除了功能适配,数据适配在开发中也很常见,比如设定数据规范,以便于在不同系统和应用程序之间进行数据交互和处理。

总结

通过以上的探讨我们可以发现,适配器模式在实际生活和编程中的应用其实是很普遍和广泛的,为了提高兼容性而增加适配器角色也是一个很常见的行为,适配器的主要功能就是帮助我们抹平差异,也就是在适配器的内部会去根据差异来做一些处理,而这些处理对用户来说是透明的,不需要了解的。

相关推荐
whysqwhw15 分钟前
js之Promise
前端
恋猫de小郭4 小时前
Flutter 3.35 发布,快来看看有什么更新吧
android·前端·flutter
chinahcp20085 小时前
CSS保持元素宽高比,固定元素宽高比
前端·css·html·css3·html5
gnip6 小时前
浏览器跨标签页通信方案详解
前端·javascript
gnip6 小时前
运行时模块批量导入
前端·javascript
DKPT6 小时前
Java设计模式之开闭原则介绍与说明
java·设计模式·开闭原则
hyy27952276847 小时前
企业级WEB应用服务器TOMCAT
java·前端·tomcat
逆风优雅7 小时前
vue实现模拟 ai 对话功能
前端·javascript·html
若梦plus7 小时前
http基于websocket协议通信分析
前端·网络协议
不羁。。7 小时前
【web站点安全开发】任务3:网页开发的骨架HTML与美容术CSS
前端·css·html