从 axios 源码学习设计模式

news2024/11/24 2:33:13

文章目录

  • 一、源码分析
    • 1.1 axios 为什么可以多种方式调用
    • 1.2 拦截器实现
      • 注册
      • 使用:promise链式调用
  • 二、从 axios 看设计模式
    • axios 的精髓在哪
    • 2.1 抽象工厂
      • axios.create -- 创建新实例的工厂
    • 2.2 微内核设计
    • 2.3 适配器思想
    • 2.4 责任链模式
    • 2.5 桥接模式
      • 举例:
      • 对于 axios来说:
      • 桥接模式和适配器模式有什么区别

本篇文章并不会从0开始对 axios 进行分析,而是对 axios 的一些关键地方进行总结
另外,本篇大部分内容都是对一些现有文章的总结
参考文章:
Axios基本原理深度解析

一、源码分析

1.1 axios 为什么可以多种方式调用

首先,我们在使用Axios的时候,会有很多种用法。是怎么实现的呢?
axios都能怎么调用

// 第一种方法
axios(config)
// 第二种
axios('example/url'[, config])
// 第三种
axios.request(config)
// 第四种
axios.get(url[, config])//'delete', 'get', 'head', 'options'请求方法一样的调用方式
// 第五种
axios.post(url[, data[, config]])// 'post', 'put', 'patch'请求方法永阳的调用方式
// 第六种
axios.all([axios1, axios2, axios3]).then(axios.spread(function (axios1response, axios2response, axios3response) {
    // 三个请求现在都执行完成
  }));
// 还可以通过axios.create方法建立自定义全局默认配置的Axios实例
axios.create(config)

总结有六种调用方式
相关代码

function createInstance(defaultConfig) {
  // 建立Axios对象
  var context = new Axios(defaultConfig);

  // Axios作者的目的是提供一个对外可用的方法。
  // 并且方法中需要用到Axios对象中的config属性和拦截器。
  // 所以要把axios原型上的方法单独拿出来,绑定context这个axios实例。
  // instance方法就是后面导出的axios实例,
  // 所以到这里位置 第一种调用方法 axios(config) 就实现了
  // 在request方法的内部,有对传入参数类型的判断,如果传入第一参数为字符串,则认为是url字符串,并且把url参数添加到第二个参数config中
  // 所以就实现了第二种调用方法axios('example/url'[, config])
  var instance = bind(Axios.prototype.request, context);

  // 这里把Axios原型上的方法和属性,扩展到instance方法上,
  // 并制定了原型上方法的this为context(上面定义axios对象)
  // Axios上有request方法,这里绑定了this为context
  // 所有第三种调用方法 axios.request(config) 就实现了
  // Axios原型中其实定义了get,post,option等等方法,
  // 所以第四种axios.get(url[, config])和第五种axios.post(url[, data[, config]])方法就实现了
  utils.extend(instance, Axios.prototype, context);

  // 这里把上面建立axios对象(context)中自有的属性方法,扩展到了instance中
  // 这样instance就有了defaults、interceptors 属性,就可以添加拦截器,操作defaultConfig了
  utils.extend(instance, context);

  return instance;
}

// 调用createInstance方法,建立了Axios实例
var axios = createInstance(defaults);

// 这里也调用上面的createInstance方法,同样建立了Axios实例,
// 只不过,这里配置了自己的config作为全局默认的config
// 所以这里实现了,通过axios.create方法建立自定义默认配置的Axios实例
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// 这里添加了all方法,其实就是promise的all方法,
// 这就是第六种调用方法,并发请求的实现原理
axios.all = function all(promises) {
  return Promise.all(promises);
};
// spread方法就是把用数组作为一个参数,变成数组的每一项为一个参数。就是为了用着方便。
axios.spread = require('./helpers/spread');

module.exports = axios;// 对外导出实例

上面注释中提到的第二种调用方法axios(‘example/url’[, config])的实现是request内部做了判断所以才能那么用。
第四种axios.get(url[, config])和第五种axios.post(url[, data[, config]])方法的实现是因为Axios原型中定义了相应的方法,所以才得以实现。
关于 Axios:

// Axios构造函数,定义l额defaults属性和interceptors属性
function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(), //拦截器,后面会讲到
    response: new InterceptorManager()
  };
}

Axios.prototype.request = function request(config) {
  // 这里对传入参数类型的判断,如果传入第一参数为字符串,
  // 则认为字符串是url,并且把url参数添加到第二个参数config中
  // 所以就实现了第二种调用方法axios('example/url'[, config])
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  // ...省略了一些代码
  }

// 这里对Axios原型扩展了'delete', 'get', 'head', 'options'方法,
// 其实都是调用了request方法
// 结合上面lib / axios.js 代码中把原型中方法扩展到了instance上
// 所以第四种方法axios.get(url[, config])就实现了
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(mergeConfig(config || {}, {
      method: method,
      url: url
    }));
  };
});

// 这里对Axios原型扩展了'post', 'put', 'patch'方法,
// 其实都是调用了request方法
// 结合上面lib / axios.js 代码中把原型中方法扩展到了instance上
// 所以第五种方法axios.post(url[, data[, config]])就实现了
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    return this.request(mergeConfig(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

1.2 拦截器实现

首先是如何使用的?
请求拦截器是先加的后执行,响应是先加的先执行

// 添加请求拦截器
const myRequestInterceptor = axios.interceptors.request.use(config => {
    // 在发送http请求之前对config做点什么
    return config; // 必须返回config否则后续http请求无法获取到config
}, error => {
    // 错误处理代码写在这里
    
    // 但是,为什么返回Promise.reject(error)
    // 这和promise的机制有关
    // 如果直接抛出error就相当于抛出一个对象,
    // 这就会运行下一级promise的fulfilled方法。并且参数是error,这个fulfilled参数应该是config才对
    // 或者运行到dispatchRequest,参数error,但dispatchRequest参数也要是config才对
    return Promise.reject(error);
});

// 添加响应拦截器
axios.interceptors.response.use(response => {
  // 对响应数据处理代码写这里
  return response; // 有且必须有一个response对象被返回
}, error => {
  // 对响应错误处理代码写这里
  
  // 同理请求拦截器,这需要返回Promise.reject(error);
  return Promise.reject(error);
});

// 移除某次拦截器
axios.interceptors.request.eject(myRequestInterceptor);

注册

axios有interceptor属性,里面有两个属性request和response,都是InterceptorManager对象,目的是存储注册的拦截器。

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

那么InterceptorManager都做了什么呢,

// 构造函数,在对象中定义handlers用来存储拦截器
function InterceptorManager() {
  this.handlers = [];
}

/**
 * 注册拦截器,并存储在handlers中
 * 参数fulfilled,用来拦截器处理数据的函数
 * 参数rejected,用来处理错误用的
 * 为什么这么设计,因为拦截器要通过Promise处理
 * 返回本条拦截器在数组handlers中的索引位置,以便提供给删除拦截器用
 */
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

/**
 * 通过注册时候返回的拦截器索引来删除拦截器
 */
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};
/**
 * 遍历拦截器的方法
 */
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

注册完成了,那么怎么发挥作用呢,那就要看看request方法了。

使用:promise链式调用

var dispatchRequest = require('./dispatchRequest');

Axios.prototype.request = function request(config) {
  // 刚进入方法,首先要处理config这里存放着http请求的必要配置
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  config = mergeConfig(this.defaults, config);

  // Set config.method
  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = 'get';
  }
  // 到这里处理完用于http请求的配置数据config
  
  // 先定义个数组,先放入一个dispatchRequest和undefined,
  // dispatchRequest前面的项目目录介绍里提到了,用来申请http请求用的
  // 为什么先建立个数组呢,作者的目的就是想先把拦截器和http请求先排好序
  // 然后再建立promise调用链,就可以一步一步的按顺序进行了
  // 入果没有理解,建议先深入研究一下Promise链式调用
  // 为什么先放dispatchRequest,又放个undefined呢
  // 可以先看一下下面怎么向chain插入拦截器的
  // 拦截器被插入到数组,并且一次向数组插入两个方法,interceptor.fulfilled, interceptor.rejected
  // 再看看后面建立promise链式调用的时候,分别用在了then的两个参数,是从数组中一起取两个的
  // 所以为了保证拦截器两个方法配对正确所以先插入[dispatchRequest, undefined]
  // 之所以用undefined,因为这里没法处理错综复杂而且多变的错误。而且这里也只能用来处理请求拦截器的错误。所以没有必要。
  // 所以用undefined,把错误抛到下面的promise,由用户定义处理方法。
  var chain = [dispatchRequest, undefined];

  // 先初始一个promise,value是config,
  // 提供给下面的promise用,也就是提供给请求拦截器用
  var promise = Promise.resolve(config);

  // 向chain数组插入请求拦截器。一对一对的插入
  // 注意这里是从前插入请求拦截器的
  // 所以用的时候,先注册的请求拦截器是后执行的,这点要注意
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  // 向chain数组插入相应拦截器。一对一对的插入
  // 相应拦截器是从后插入的
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });
  // 到此,把拦截器和http请求拍好顺序了
  // 下面就利用这个循序建立一个promise链
  // promise链让拦截器和http请求按照顺序执行了,执行顺序是:
  // 请求拦截器->http请求->相应拦截器
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

到这里,已经建立了一个promise链。先执行请求拦截器,按照需求修改config。然后执行http请求。请求结果,传到相应拦截器处理。最后抛出链调用最后的promise,我们用这个promise就能得到这一串处理的最终结果了。
在这里插入图片描述
在这里插入图片描述

二、从 axios 看设计模式

axios 的精髓在哪

通过简单学习 axios 源码,就会发现,它的源码并不难,比 vue 要简单很多。 axios 的精髓在于他已经迭代了40个版本,
npm 的 version 规则是首个版本号变化表示 api 不向下兼容,现在 axios 的主版本号已经从 0.x 迭代到了 1.x, 按理说应该和 vue 一样,是有 重大变更的。 而 axios 增加了这么多功能。却始终保持没有 api 明显变化。主要原因是 axios 内部使用了多种设计模式和架构模式。
比如 适配器,桥接,代理,抽象工厂,微内核设计,还有一些设计原则,axios里面都用的非常好。
所以,当我们在用任何版本的 axios 除了一些 bug 以外,没有什么兼容问题。

2.1 抽象工厂

工厂模式将对象的创建和实现分离(尤其是写库的时候,经常需要把创建和实现进行分离)。
使代码具备良好的封装性,可扩展性(符合开放封闭原则),高解耦性(符合最少知识原则(说白了,就是让你能够傻瓜式应用))。

为什么工厂模式返回的不是axios实例,而是axios.request?
因为我们要进行封装,把实例给用户,体现我们工厂模式的优点。假如我们返回axios实例,就造成了无法传config了,这就很糟糕,所以我们返回了方法,就能传参数了。就能同时满足axios({})和axios.request({})
妙啊

import Axios from './core/Axios';  // 引入Axios,是放axios的核心代码的
import { extend } from './helper/utils'; // 混入方法

// 工厂
function createInstance() {
  const axios = new Axios();
  const req = axios.request.bind(axios); //这里与源码有出入,但是意思一直,如果追求与源码的一致性,可以回头看看上面的源码分析部分
  // req继承axios的属性和方法
  // Object.getOwnPropertyNames(Axios.prototype)读取对象属性,可以读取不可不可遍历的属性
  extend(req, Object.getOwnPropertyNames(Axios.prototype), axios);
  extend(req, Object.getOwnPropertyNames(axios), axios);
  return req;
}

const axios = createInstance(); // 创建实例的工厂
export default axios;

axios.create – 创建新实例的工厂

axios.create给用户提供了自定义配置的接口,通过调用mergeConfig()来合并用户配置和默认配置。从而我们可以得到一个自定义的instance

axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

2.2 微内核设计

Axios 是一个基于 Promise 的 HTTP 客户端库,它使用了微内核设计。微内核是一种软件设计模式,将核心功能与可选功能分离,通过插件机制实现灵活扩展。
在 Axios 的源码中,它的微内核设计可以总结为以下几个主要的组件:

  1. 核心库:核心库提供了基本的请求和响应处理逻辑,包括创建 XMLHttpRequest 对象、设置请求配置、发送请求、处理响应等。它是 Axios 的核心部分,负责处理最基本的 HTTP 请求和响应。
  2. 拦截器:拦截器是一种插件机制,可以在请求和响应的过程中插入自定义的逻辑。Axios 通过 request 拦截器和 response 拦截器来实现请求和响应的拦截和修改。拦截器可以用于添加公共请求头、请求参数的处理、错误的全局处理等。
  3. 转换器:转换器允许你在发送请求之前或接收到响应之后对数据进行自定义转换。Axios 提供了请求和响应数据的转换器接口,可以通过设置 transformRequest 和 transformResponse 来定制数据的格式化和解析。
  4. 取消操作:Axios 允许你通过创建一个 CancelToken 实例来取消请求。取消请求可以通过 cancel 方法来触发,调用 cancel 方法后,Axios 将会中止该请求并抛出一个 Cancel 类型的错误。
  5. 错误处理:Axios 对错误的处理进行了统一的封装。它使用 Promise 的错误处理机制,当请求或响应发生错误时,会通过 Promise 的 reject 来返回错误信息。此外,Axios 也提供了全局的错误处理机制,你可以通过配置 onError 来捕获并处理全局的错误。
  6. 扩展机制:Axios 支持通过自定义适配器、拦截器、转换器等机制来扩展其功能。通过这些机制,你可以添加自定义的逻辑、处理非标准的返回数据类型、修改请求配置等。这使得 Axios 的功能非常灵活和可扩展。
    以上是 Axios 中微内核设计的主要组件和机制。这种设计使得 Axios 的核心库非常精简和高效,同时可以根据需求灵活地扩展和定制功能。

首先 core 提供核心功能,然后各种功能:拦截器,错误处理,取消操作,转换器 都是重开一个文件夹,然后在 core 中进行引用

axios 的 目录:

├── /dist/                     # 项目输出目录
├── /lib/                      # 项目源码目录
│ ├── /cancel/                 # 定义取消功能
│ ├── /core/                   # 一些核心功能
│ │ ├── Axios.js               # axios的核心主类
│ │ ├── dispatchRequest.js     # 用来调用http请求适配器方法发送请求
│ │ ├── InterceptorManager.js  # 拦截器构造函数
│ │ └── settle.js              # 根据http响应状态,改变Promise的状态
│ ├── /helpers/                # 一些辅助方法
│ ├── /adapters/               # 定义请求的适配器 xhr、http
│ │ ├── http.js                # 实现http适配器
│ │ └── xhr.js                 # 实现xhr适配器
│ ├── axios.js                 # 对外暴露接口
│ ├── defaults.js              # 默认配置 
│ └── utils.js                 # 公用工具
├── package.json               # 项目信息
├── index.d.ts                 # 配置TypeScript的声明文件
└── index.js                   # 入口文件

2.3 适配器思想

axios 是 适配浏览器和node,兼容性,对外统一接口
axios在浏览器端使用XMLHttpRequest对象发送ajax请求;在node环境使用http对象发送ajax请求。

var defaults.adapter = getDefaultAdapter();
function getDefaultAdapter () {
	var adapter;
    if (typeof XMLHttpRequest !== 'undefined') {
    	// 浏览器环境
        adapter = require('./adapter/xhr');
    } else if (typeof process !== 'undefined') {
    	// node环境
        adapter = require('./adapter/http');
    }
   return adapter;
}

2.4 责任链模式

一个链条,上面有很多职责。
他的好处是链条上的各个职责,只需要关心自己的事情就行了,不需要知道自己的上一步是什么,下一步是什么,跟上下的职责都不耦合,这样当上下职责变化了,自己也不受影响,往链条上添加或者减少职责也非常方便。

Axios的拦截器有请求拦截器和响应拦截器,执行的顺序是请求拦截器 -> 发起请求 -> 响应拦截器,这其实就是一个链条上串起了三个职责。

// 先从用法入手,一般我们添加拦截器是这样写的 
// instance.interceptors.request.use(fulfilled, rejected)
// 根据这个用法我们先写一个Axios类。
function Axios() {
  // 实例上有个interceptors对象,里面有request和response两个属性
  // 这两个属性都是InterceptorManager的实例
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

// 然后是实现InterceptorManager类
function InterceptorManager() {
  // 实例上有一个数组,存储拦截器方法
  this.handlers = [];
}

// InterceptorManager有一个实例方法use
InterceptorManager.prototype.use = function(fulfilled, rejected) {
  // 这个方法很简单,把传入的回调放到handlers里面就行
  this.handlers.push({
    fulfilled,
    rejected
  })
}

这些拦截器方法都是什么时候执行呢?当然是我们调用instance.request的时候,调用instance.request的时候真正执行的就是请求拦截器 -> 发起请求 -> 响应拦截器链条,所以我们还需要来实现下Axios.prototype.request:

Axios.prototype.request = function(config) {
  // chain里面存的就是我们要执行的方法链条
  // dispatchRequest是发起网络请求的方法,本文主要讲设计模式,这个方法就不实现了
  // chain里面先把发起网络请求的方法放进去,他的位置应该在chain的中间
  const chain = [dispatchRequest, undefined];
  
  // chain前面是请求拦截器的方法,从request.handlers里面取出来放进去
  this.interceptors.request.handlers.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });
  
  // chain后面是响应拦截器的方法,从response.handlers里面取出来放进去
  this.interceptors.response.handlers.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });
  
  // 经过上述代码的组织,chain这时候是这样的:
  // [request.fulfilled, request.rejected, dispatchRequest, undefined, response.fulfilled,  
  // response.rejected]
  // 这其实已经按照请求拦截器 -> 发起请求 -> 响应拦截器的顺序排好了,拿来执行就行
  
  let promise = Promise.resolve(config);   // 先来个空的promise,好开启then
  while (chain.length) {
    // 用promise.then进行链式调用
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
}

注意,请求拦截器用了 unshift 方法,响应拦截器用了 push 方法
是因为:请求拦截器是先加的后执行,响应是先加的先执行

2.5 桥接模式

首先,什么是桥接模式,它主要是优化不同维度,从 3*3 变成 3+1
桥接模式是一种结构设计模式,用于将抽象部分和其实现部分解耦,并使它们能够独立地变化。它通过将抽象部分与实现部分之间建立一个桥接(Bridge)来实现解耦。

桥接模式人如其名,其实就相当于一个桥梁,把不同维度的变量桥接在一起来实现功能。假设我们需要实现三种形状(长方形,圆形,三角形),每种形状有三种颜色(红色,绿色,蓝色),这个需求有两个方案,一个方案写九个方法,每个方法实现一个图形:

function redRectangle() {}
function greenRectangle() {}
function blueRectangle() {}
function redCircle() {}
function greenCircle() {}
function blueCircle() {}
function redTriangle() {}
function greenTriangle() {}
function blueTriangle() {}

上述代码虽然功能实现了,但是如果我们需求变了,我们要求再加一个颜色,那我们就得再加三个方法,每个形状加一个。这么多方法看着就很重复,意味着他有优化的空间。我们仔细看下这个需求,我们最终要画的图形有颜色和形状两个变量,这两个变量其实是没有强的逻辑关系的,完全是两个维度的变量。那我们可以将这两个变量拆开,最终要画图形的时候再桥接起来,就是这样:

function rectangle(color) {     // 长方形
  showColor(color);
}

function circle(color) {     // 圆形
  showColor(color);
}

function triangle(color) {   // 三角形
  showColor(color);
}

function showColor(color) {   // 显示颜色的方法
  
}

// 使用时,需要一个红色的圆形
let obj = new circle('red');

使用桥接模式后我们的方法从3 * 3变成了3 + 1,而且如果后续颜色增加了,我们只需要稍微修改showColor方法,让他支持新颜色就行了。如果我们变量的维度不是2,而是3,这种优势会更加明显,前一种需要的方法是x * y * z个,桥接模式优化后是x + y + z个,这直接就是指数级的优化。所以这里桥接模式优化的核心思想是观察重复代码能不能拆成多个维度,如果可以的话就把不同维度拆出来,使用时再将这些维度桥接起来。

举例:

桥接模式其实最形象的例子就是毛笔和蜡笔,因为这个例子非常直观,好理解。这个例子的需求是要画细,中,粗三种型号的线,每种型号的线需要5种颜色,如果我们用蜡笔来画就需要15支蜡笔,如果我们换毛笔来画,只需要3支毛笔就行了,每次用不同颜色的墨水,用完换墨水就行。写成代码就是这样,跟上面那个有点像:

// 先来三个笔的类
function smallPen(color) {
  this.color = color;
}
smallPen.prototype.draw = function() {
  drawWithColor(this.color);    // 用color颜色来画画
}

function middlePen(color) {
  this.color = color;
}
middlePen.prototype.draw = function() {
  drawWithColor(this.color);    // 用color颜色来画画
}

function bigPen(color) {
  this.color = color;
}
bigPen.prototype.draw = function() {
  drawWithColor(this.color);    // 用color颜色来画画
}

// 再来一个颜色类
function color(color) {
  this.color = color;
}

// 使用时
new middlePen(new color('red')).draw();    // 画一个中号的红线
new bigPen(new color('green')).draw();     // 画一个大号的绿线

对于 axios来说:

在 Axios 中,抽象部分可以被看作是对 HTTP 请求的封装,而实现部分则表示不同平台或环境下的具体实现方式,例如浏览器环境和 Node.js 环境。
Axios 通过提供统一的 API 接口,将不同平台下的 HTTP 请求细节抽象出来,使得开发者可以使用相同的代码来发送请求,而无需关心底层实现的差异。
比如,我们需要发送 get、post 请求,并且需要在 浏览器 和 node 端运行,那么本来是 2*2
而对 get、post 请求统一封装,将它变成 1,那么此时就变成了 1*2
我们可以看到无论是发送 GET 请求还是 POST 请求,使用的都是相同的 axios 对象和函数调用方式。这使得开发者可以在不同的平台和环境下保持一致的编码风格和API调用方式。
Axios 库内部根据当前运行环境的不同,会选择合适的实现方式来发送 HTTP 请求,例如在浏览器环境中使用 XMLHttpRequest 或 Fetch API,在 Node.js 环境中使用基于 HTTP 模块或第三方库如 http 或 https。
通过这种设计,Axios 实现了抽象部分(HTTP 请求)与实现部分(底层网络传输)之间的解耦,使得开发者可以专注于业务逻辑而无需关心底层实现细节,体现了类似于桥接模式的设计思想。

桥接模式和适配器模式有什么区别

桥接模式(Bridge Pattern)和适配器模式(Adapter Pattern)虽然在某些方面有一些相似之处,但它们在设计目的和实现方式上有着明确的区别。

  1. 设计目的:
    ● 桥接模式的设计目的是将抽象部分与实现部分分离,使它们可以独立变化。桥接模式主要关注如何通过组合来实现不同维度的变化。它的目标是减少抽象和实现之间的耦合,提供更灵活的扩展性。
    ● 适配器模式的设计目的是在不兼容的接口之间进行适配,使它们能够一起工作。适配器模式主要关注如何通过转换接口来实现不同接口之间的协同工作。它的目标是提供接口转换,以实现两个不兼容接口之间的适配。
  2. 功能和用途:
    ● 桥接模式通过将抽象部分和实现部分分开,允许它们可以独立变化。桥接模式可以在两个维度上进行变化,例如在多个平台上实现多个颜色的图形。
    ● 适配器模式主要用于两个不兼容接口之间的协同工作。适配器模式用于兼容两个不同接口,使它们能够一起工作。
    桥接模式 是 二维 的,适配器模式是 一维 的。
  3. 关注的对象:
    ● 桥接模式关注的是如何将抽象部分和实现部分分离,并让它们可以独立变化。它关注的是系统的结构层面。
    ● 适配器模式关注的是两个不兼容接口之间的适配,使它们能够一起工作。它关注的是接口之间的协同工作。
  4. 实现方式:
    ● 桥接模式通过将抽象部分和实现部分分开,并通过组合的方式实现不同维度的变化。抽象部分和实现部分可以独立扩展而不会相互影响。
    ● 适配器模式通常通过包装或继承来转换接口,使两个接口能够一起工作。适配器模式兼容已有的代码,通过适配器来调用原有接口,以实现两个接口之间的协同工作。

总结起来,桥接模式主要关注在抽象部分和实现部分之间的分离和独立变化,而适配器模式主要关注两个不兼容接口之间的适配和协同工作。它们在设计目的、功能和实现方式上有明显的差异。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/795315.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

elevation mapping学习笔记1之Ubuntu18.04+ROS-melodic编译安装elevation mapping高程图及示例运行

文章目录 0 引言1 安装依赖1.1 grid map1.2 Eigen1.3 kindr1.4 Point Cloud Library (PCL) 2 编译和问题解决3 运行示例3.1 turtlesim3_waffle_demo3.2 simple_demo 和 Ground Truth Demo 0 引言 苏黎世开源的elevation mapping指的是苏黎世联邦理工学院(ETH Zuric…

TypeScript -- 类

文章目录 TypeScript -- 类TS -- 类的概念创建一个简单的ts类继承 public / private / protected-- 公共/私有/受保护的public -- 公共private -- 私有的protected -- 受保护的 其他特性readonly -- 只读属性静态属性 -- static修饰ts的getter /setter抽象类abstract TypeScrip…

【ArcGIS Pro二次开发】(53):村规制表、制图【福建省】

这篇算是村规入库的一个延续。 村庄规划中有一些图纸是需要严格按照规范制图,或形成一定规范格式的。 这些图纸的制作基本算是机械式的工作,可以用工具来代替人工。 一、要实现的功能 如上图所示,在【村庄规划】组,新增了两个工…

虚拟机中的win10连不上网怎么办?

安装VMware 16和在其中安装win10系统参考这篇,很有详细且有用。 这篇主要记录我安装后发现虚拟机中win10连不上网了,查了好多,终于有一个方法弄好了。 1.打开主机的网络连接 双击所连接的网络的属性——>共享 ,将小勾勾全部勾…

UE5 动画蓝图模板(Animation Blueprint Template)

文章目录 前言准备内容创建动画蓝图使用动画蓝图模板示例1示例2总结前言 本文基于虚幻5.2版本介绍制作动画蓝图模板,本教程要求使用虚幻5.0及以上版本。 准备内容 使用第三人称游戏内容包,已添加可忽略。 选择第三人称游戏,添加到项目。 创建动画蓝图 在 Characters 文件…

前端vue2 全局水印效果

最近写项目遇到一个需求,全局显示水印,不管在哪个路由都要显示。 想要实现的效果: 新建shuiyin.js文件 // 定义水印函数 const addWatermark ({container document.body, // 水印添加到的容器,默认为 bodywidth "200px&…

java代码审计6之ssrf

文章目录 1、java支持的网络请求协议:2、Java 中能发起⽹络请求的类2.1、仅⽀持 HTTP/HTTPS 协议的类2.2、⽀持 sun.net.www.protocol 所有协议的类2.3、审计关键词 3、靶场3.1、漏洞代码13.2、ftp协议读取技巧3.3、无回显之探测内网3.4、无回显之探测文件 之前的文…

代码随想录额外题目| 链表 ●234回文链表●143重排链表 ●141环形链表

#234回文链表 简单方法很简单&#xff08;转成vector判断&#xff09;&#xff0c;难的方法有点难, 很巧妙 简单方法&#xff1a; bool isPalindrome(ListNode* head) {vector<int> vec;ListNode* curhead;while(cur){vec.push_back(cur->val);curcur->next;}i…

Window和linux使用samba实现文件共享

开发环境 开发平台&#xff1a;IMX6 虚拟机环境&#xff1a;Ubuntu16.04 Samba版本&#xff1a;3.4.17 目的 实现无论IMX6作为客户端还是服务端&#xff0c;IMX6系统下与window系统、ubuntu系统文件共享。 Samba移植 下载Samba源码,这个网上一搜大把&#xff0c;我用的版本…

C++ | 构造与析构

目录 普通构造 构造函数基础 显示调用与隐式调用 explicit关键字 初始化列表 拷贝构造 拷贝构造的写法 深拷贝和浅拷贝 拷贝构造的调用时机 返回值优化 析构函数 析构函数基础 析构函数的作用 注意事项 如果无法调用构造函数&#xff0c;那么就无法实例化出对象…

python进阶书籍的推荐 知乎,python入门后如何进阶

本篇文章给大家谈谈python进阶书籍的推荐 知乎&#xff0c;以及python入门后如何进阶&#xff0c;希望对各位有所帮助&#xff0c;不要忘了收藏本站喔。 1、Python应该怎么学_python应该怎么学 想要学习Python&#xff0c;需要掌握的内容还是比较多的&#xff0c;对于自学的同…

java设计模式之模板模式(结合示例)

文章目录 &#x1f4cb;模板模式概念&#x1f4d6;组成要素&#x1f516;代码示例 &#x1f4c8;总结 &#x1f4cb;模板模式概念 在Java中&#xff0c;模板模式&#xff08;Template Pattern&#xff09;是一种行为设计模式&#xff0c;它定义了一个操作中的算法框架&#xf…

Unity游戏源码分享-街机捕鱼2017版本

Unity游戏源码分享-街机捕鱼2017版本 完整的单机游戏 功能玩法&#xff0c;积分系统都已经齐全的了。 项目源码地址&#xff1a;https://download.csdn.net/download/Highning0007/88105459

音视频——帧内预测

H264编码(帧内预测) 在帧内预测模式中&#xff0c;预测块P是基于已编码重建块和当前块形成的。对亮度像素而言&#xff0c;P块用于44子块或者1616宏块的相关操作。44亮度子块有9种可选预测模式&#xff0c;独立预测每一个44亮度子块&#xff0c;适用于带有大量细节的图像编码&…

C语言中的函数(超详细)

C语言中的函数&#xff08;超详细&#xff09; 一、函数概述二、C语言中函数的分类1.库函数2.自定义函数三、函数的参数1.实际参数&#xff08;实参&#xff09;2.形式参数&#xff08;形参&#xff09;四、函数的调用1.传值调用2.传址调用五、函数的嵌套调用和链式访问1.嵌套调…

【手机】三星手机刷机解决SecSetupWizard已停止

三星手机恢复出厂设置之后&#xff0c;出现SecSetupWizard已停止的解决方案 零、问题 我手上有一部同学给的三星 GT-S6812I&#xff0c;这几天搞了张新卡&#xff0c;多余出的卡就放到这个手机上玩去了。因为是获取了root权限的&#xff08;直接使用KingRoot就可以&#xff0…

C++ 提高编程

C 提高编程 主要针对C泛型编程和STL技术 一、 模板 1、 概念 模板就是建立通用的模具&#xff0c;大大提高代码的复用性 模板特点 模板不可以直接使用&#xff0c;它只是一个框架 ​ 模板的通用并不是万能的 2、 函数模板 C 另一种编程思想为泛型编程&#xff0c;主要利用的…

ChatGPT如何帮助学生学习

​ 一些教育工作者担心学生可能使用ChatGPT作弊。因为这个AI工具能写报告和计算机代码&#xff0c;画出复杂图表……甚至已经有许多学校把ChatGPT屏蔽。 研究发现&#xff0c;学生作弊的主要原因是想考得好。是否作弊与作业和考试的打分方式有关&#xff0c;所以这与技术的便…

浅谈前端跨平台框架

概述 前端跨端实践是指在开发过程中&#xff0c;使用统一的代码库或框架来实现在不同平台上运行的应用程序。 这种实践旨在减少重复开发和维护成本&#xff0c;并提高开发效率和用户体验。 以下是一些前端跨端实践的方法和技术&#xff1a; 响应式设计&#xff08;Responsiv…

0-虚拟机补充知识

虚拟机克隆 如果想要构建服务器集群&#xff0c;没有必要一台一台的去进行安装&#xff0c;只要通过克隆就可以。 快速获得多台服务器主要有两种方式&#xff0c;分别为&#xff1a;直接拷贝操作和vmware的克隆操作 直接拷贝 将之前安装虚拟机的所有文件进行拷贝&#xff0…