从 axios 源码学习设计模式
文章目录
- 一、源码分析
- 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.methodif (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 的源码中,它的微内核设计可以总结为以下几个主要的组件:
- 核心库:核心库提供了基本的请求和响应处理逻辑,包括创建 XMLHttpRequest 对象、设置请求配置、发送请求、处理响应等。它是 Axios 的核心部分,负责处理最基本的 HTTP 请求和响应。
- 拦截器:拦截器是一种插件机制,可以在请求和响应的过程中插入自定义的逻辑。Axios 通过 request 拦截器和 response 拦截器来实现请求和响应的拦截和修改。拦截器可以用于添加公共请求头、请求参数的处理、错误的全局处理等。
- 转换器:转换器允许你在发送请求之前或接收到响应之后对数据进行自定义转换。Axios 提供了请求和响应数据的转换器接口,可以通过设置 transformRequest 和 transformResponse 来定制数据的格式化和解析。
- 取消操作:Axios 允许你通过创建一个 CancelToken 实例来取消请求。取消请求可以通过 cancel 方法来触发,调用 cancel 方法后,Axios 将会中止该请求并抛出一个 Cancel 类型的错误。
- 错误处理:Axios 对错误的处理进行了统一的封装。它使用 Promise 的错误处理机制,当请求或响应发生错误时,会通过 Promise 的 reject 来返回错误信息。此外,Axios 也提供了全局的错误处理机制,你可以通过配置 onError 来捕获并处理全局的错误。
- 扩展机制: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,好开启thenwhile (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)虽然在某些方面有一些相似之处,但它们在设计目的和实现方式上有着明确的区别。
- 设计目的:
● 桥接模式的设计目的是将抽象部分与实现部分分离,使它们可以独立变化。桥接模式主要关注如何通过组合来实现不同维度的变化。它的目标是减少抽象和实现之间的耦合,提供更灵活的扩展性。
● 适配器模式的设计目的是在不兼容的接口之间进行适配,使它们能够一起工作。适配器模式主要关注如何通过转换接口来实现不同接口之间的协同工作。它的目标是提供接口转换,以实现两个不兼容接口之间的适配。 - 功能和用途:
● 桥接模式通过将抽象部分和实现部分分开,允许它们可以独立变化。桥接模式可以在两个维度上进行变化,例如在多个平台上实现多个颜色的图形。
● 适配器模式主要用于两个不兼容接口之间的协同工作。适配器模式用于兼容两个不同接口,使它们能够一起工作。
桥接模式 是 二维 的,适配器模式是 一维 的。 - 关注的对象:
● 桥接模式关注的是如何将抽象部分和实现部分分离,并让它们可以独立变化。它关注的是系统的结构层面。
● 适配器模式关注的是两个不兼容接口之间的适配,使它们能够一起工作。它关注的是接口之间的协同工作。 - 实现方式:
● 桥接模式通过将抽象部分和实现部分分开,并通过组合的方式实现不同维度的变化。抽象部分和实现部分可以独立扩展而不会相互影响。
● 适配器模式通常通过包装或继承来转换接口,使两个接口能够一起工作。适配器模式兼容已有的代码,通过适配器来调用原有接口,以实现两个接口之间的协同工作。
总结起来,桥接模式主要关注在抽象部分和实现部分之间的分离和独立变化,而适配器模式主要关注两个不兼容接口之间的适配和协同工作。它们在设计目的、功能和实现方式上有明显的差异。
相关文章:

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

输出不同程序执行的时间
简单的测试工具代码,它可以输出不同程序执行的时间。我们可以使用Python的time模块来实现这个功能。 import timedef test_function(func, *args, **kwargs):"""测试函数执行时间的工具函数:param func: 待测试的函数:param *args: 函数的位置参数:…...

HDU 6391 组合数学 + DP
题意 传送门 HDU 6391 Lord Li’s problem 题解 仅考虑 S i ≠ T i S_i\neq T_i SiTi 的数量 m m m,最后答案除以 ( n m ) \binom{n}{m} (mn) 即可。考虑 X X X 的排列,最后答案除以 k ! k! k! 即可。 d p [ i 1 ] [ j ] dp[i1][j] dp[…...

StopWatch与ThreadLocal
目录 1、StopWatch 1、1作用: 1、2方法: 1、3使用方法 2、ThreadLocal 2、1什么是ThreadLocal 2、2简单例子 2、3使用ThreadLocal带来的四个好处 2、4主要方法 2、5ThreadLocal内存泄漏问题 1、StopWatch 1、1作用: 统计代码块耗时时…...

20. 有效的括号
给定一个只包括 (,),{,},[,] 的字符串 s ,判断字符串是否有效。 有效字符串需满足: 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都有一个对应的相同类型的左括…...

微信小程序原生写法传递参数
微信小程序原生写法传递参数 data-xxx 自定义参数名 ,接收参数:方法(变量名) checkVip:function(event) {let that thisconsole.log(event,event)console.log(event.currentTarget.dataset.idx,index)let index Number(eve…...

JavaWeb+jsp+Tomcat的教务查询系统
点击以下链接获取源码: https://download.csdn.net/download/qq_64505944/88134601?spm1001.2014.3001.5503 jsp/tomcat7.05/MySQL5.7或8版本/ssm框架/spring/ Web框架:SpringBoot/ORM框架:Mybatis/安全框架:Shiro/分页插件&am…...

C# FTP下载 采用Ssh.Net方式
不要再用FTPClient了 nuget下载Ssh.Net 然后代码如下: /// <summary>/// SFTP操作类/// </summary>public class SFTPHelper{#region 字段或属性private SftpClient sftp;/// <summary>/// SFTP连接状态/// </summary>public bool Conne…...

【C++】做一个飞机空战小游戏(三)——模块化程序设计
[导读]本系列博文内容链接如下: 【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值 【C】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动【C】做一个飞机空战小游戏(三)——模块化程设设计 在前两讲当中,介绍了利用…...

Django使用WebSocket
1、websocket 相关 实现一个系统,20 个用户同时打开网站,呈现出来一个群聊界面 解决方案 轮询:让浏览器每隔2s向后台发送一次请求,缺点:延迟,请求太多网站压力大 长轮询:客户端向服务端发送请…...

看完这篇 教你玩转渗透测试靶机Vulnhub——HarryPotter:Nagini
Vulnhub靶机HarryPotter:Nagini渗透测试详解 Vulnhub靶机介绍:Vulnhub靶机下载:Vulnhub靶机安装:Vulnhub靶机漏洞详解:①:信息收集:②:漏洞发现:③:SSRF漏洞利用…...

IPO要收紧?业内人士未予以完全确认
“IPO全面收紧、吃穿住等行业标的基本劝退(除非行业龙头)、科创板第五套标准暂停受理……”在上周末,一篇关于IPO收紧的“小作文”在投行圈内疯狂转发。 距离全面注册制正式实施已过去了5个半月,IPO节奏是否在发生较大变化&#…...

stable difussion Pytorch实现与测试
引言: Stable Diffusion是目前最火的AI绘画工具之一,它是一个免费开源的项目,可以被任何人免费部署和使用。通过Stable Diffusion,可以很轻松的通过文字描述,生成对应的图片。由于它是一个开源项目,开源社区(如:GitHub)中有很多插件和训练好的模型,我们可以直接使用。…...

Redis简述
Redis是什么Redis数据类型Redis应用场景缓存计数器分布式会话排行榜最新列表分布式锁消息队列 Redis出现的问题穿透击穿雪崩 Redis为什么速度快 Redis是什么 redis是一种高速缓存数据库 Redis数据类型 string hash list set zset Redis应用场景 缓存 Redis作为缓存层&…...

Redis 操作List
【分布式】Redis 分布式之List_redissonclient.getlist_比嗨皮兔的博客-CSDN博客 说明 配置文件参考:https://blog.csdn.net/qq_38428623/article/details/123217001?utm_sourceapp&app_version5.1.1&codeapp_1562916241&uLinkIdusr1mkqgl919blen ——…...

多个List 合并变成一个List+一个List 根据某个字段相等的另一个字段相加,并排序变成新的List
List<CurveTimeAndValueDomain> curves new ArrayList<>();for (int i 0; i < columnNames.size(); i){if (columnNames.get(i).equals(PlantConstant.TENDOWNFX) || columnNames.get(i).equals(PlantConstant.TENDOWNQP)) {//10千伏以下 数据 进行缓慢处理cu…...

华为流程体系:流程架构「OES方法」
目录 内容简介 OES方法 端到端的流程 专栏列表 CSDN学院 作者简介 内容简介 今天继续来谈谈华为流程体系中的流程架构。 在前期的内容已经介绍过 POS 流程架构的方法。 这里就先回顾一下 POS 方法的相关内容: 关于 POS,大家可以参看上面的这张图…...

c# 创建一个未定义类的临时对象列表
使用场景:要使用的数据太多,列表/字典无法满足需求,需要传入对象,但是又不想创建模型 new[] 是一种用于创建匿名类型数组的写法。它是 C# 中的一种语法糖,用于简化数组的初始化过程。 在下面代码示例中,ne…...

el-button增加下载功能
vue3和element-plus <el-uploadv-model:file-list"fileList"action"/api/upload"multiple:limit"1":headers"headers" ><el-button type"primary">选择文件</el-button><template #file"{ file …...

prometheus和cAdvisor组合
文章目录 docker内部署PromethuesPrometheuscAdvisorPrometheus和cAdvisor关系配置 docker内部署Promethues Prometheus Prometheus是一个开源的系统监控和报警工具,由SoundCloud开发并在2012年捐赠给了Cloud Native Computing Foundation (CNCF)。它被广泛用于监…...

计算机网络(2) --- 网络套接字UDP
计算机网络(1) --- 网络介绍_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/131967378?spm1001.2014.3001.5501 目录 1.端口号 2.TCP与UDP协议 1.TCP协议介绍 1.TCP协议 2.UDP协议 3.理解 2.网络字节序 发送逻辑…...

Idea 结合docker-compose 发布项目
Idea 结合docker-compose 发布项目 这里写目录标题 Idea 结合docker-compose 发布项目Docker 开启远程访问功能 添加相应端口配置IDEA 链接Docker配置项目 docker-compose.yml本地还需要安装 dockerwin11 安装本地Docker 可能存在问题 Linux内核不是最新 Docker 开启远程访问功…...

django
django学习 初识Django1.安装django2.创建项目2.1 在终端2.2 Pycharm 3. 创建app4.快速上手4.1 再写一个页面4.2 templates模板4.3 静态文件4.3.1 static目录4.3.2 引用静态文件 5.模板语法案例:伪联通新闻中心6.请求和响应案例:用户登录7.数据库操作7.1…...

c++游戏框架
游戏类 class Sprite { public:Sprite(int x, int y, int w, int h, const char* imagePath);~Sprite();void render(SDL_Renderer* renderer);void move(int x, int y); private:SDL_Texture* texture_;SDL_Rect rect_; }; 物理引擎类 class PhysicsEngine { public:Physi…...

v-model绑定checkbox无法动态更新视图
在vue2中使用v-model绑定checkbox <input type"checkbox" v-model"isChecked" :valueisChecked change"handleCheckboxChange" />监听change事件,并在change事件中做一些特殊处理,比如用户在登录时有没有阅读过隐私…...

原生html—摆脱ps、excel 在线绘制财务表格加水印(html绘制表格js加水印)
文章目录 ⭐前言⭐html标签💖table表格的属性💖实现财务报表 ⭐结束 ⭐前言 大家好,我是yma16,本文分享原生html——绘制表格报表加水印。 背景:解决没有ps的情况下使用前端html制作表格报表。 html介绍 HTML…...

微信小程序配置上传多个u-upload上传
微信小程序配置上传多个u-upload上传 使用的是uView框架 微信小程序配置上传多个u-upload上传图片 场景需求:根据PC端配置项追加图片配置 小程序根据配置的图片数量,图片名称,进行上传图片 难度在于 我们不知道用户会追加多少个图片配置字段 …...

python使用win32com库实现对Excel的操作
使用win32com库实现对Excel的操作 1. 引言 在日常工作中,我们经常需要对Excel文件进行操作,例如读取和写入数据、格式化和样式、插入和删除等。而使用Python的win32com库,我们可以通过代码来实现对Excel的自动化操作,提高工作效…...

<Maven>项目依赖导入Maven本地仓库命令
项目工程pom.xml文件打开:查看报错的依赖, 将jar包放在D盘(或者其它路径都可)根目录下,在windows黑窗口执行以下命令; 举例:jar包名称: 1.api-1.0-SNAPSHOT102.jar 2.coms-cache-1.0-SNAPSHOT.jar 命令: mvn install:install-fi…...

爬虫006_python中的运算符_算术运算符_赋值运算符_复合赋值运算符_比较运算符_逻辑运算符_逻辑运算符性能提升---python工作笔记024
首先看加减乘除 然后看这里的 // 是取整数部分,不是四舍五入 然后%这个是取余数 然后**是,几次方那种 指数...