• 设计模式:适配器模式


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

    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对象进行封装的:

    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中我们知道,函数也是对象,所以我们定义如下函数:

    async function AjaxAdapter(method, url, {query, params, headers, successCallback }) {
      // ...
    }
    

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

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

    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函数即可。

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

    总结

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

  • 相关阅读:
    项目沟通和干系人管理
    三级分类部分三级目录无法加载,后端接口能在前端返回所有数据
    点亮一个LED+LED闪烁+LED流水灯——“51单片机”
    js 利用slice做分页的坑
    sklearn实现逻辑回归_以python为工具【Python机器学习系列(十)】
    Windows系统配置高精度时间服务
    OSPF的网络类型
    洛谷 T284656 改试卷(paper)
    SpringBoot实现上传存储图片到七牛云服务器
    [附源码]计算机毕业设计springboot贷款申请审核管理系统论文
  • 原文地址:https://www.cnblogs.com/beckyyyy/p/18011047