设计模式系列:
前端设计模式之工厂模式
前端设计模式之代理模式
前端设计模式之策略模式
前端设计模式之装饰模式
责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。(此处引自 gof 设计模式)
在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,所以责任链将请求的发送者和请求的处理者解耦了。
责任链模式是一种对象行为型模式,其主要优点如下:
其主要缺点如下。
责任链模式,总的一个核心就是请求者不必知道是谁哪个节点对象处理的请求,由于处理请求的可以在不同对象下处理,所以请求者跟接受者是解耦的。
纯的责任链:要求请求在这些对象链中必须被处理,而且一个节点处理对象,要么只处理请求,要么把请求转发给下个节点对象处理;
不纯的责任链:要求在责任链里不一定会有处理结构,而且一个节点对象,即可以处理部分请求,并把请求再转发下个节点处理;
责任链模式对前端开发来说可能有点陌生,但是看了前面的描述又感觉似曾相识
实际上 express、redux 里的 middleware 都可以简单理解为责任链模式的运用
要实现中间件模式,最重要的实现细节是:
class Middleware {
constructor() {
this.$cache = []
this.$middlewares = []
}
// 注册中间件
use() {
[...arguments].forEach(item => {
if (typeof item === 'function') {
this.$cache.push(item)
}
})
return this
}
/**
* 每个中间件只有两个形参 第一是传进来的参数 第二个是调用下一个中间件的函数
* 中间件的执行顺序是根据你注册中间件的顺序来去调用的
*/
next(params) {
while (this.$middlewares.length) {
const ware = this.$middlewares.shift()
ware.call(this, params, this.next.bind(this))
}
}
execute(params) {
this.$middlewares = this.$cache.map(fn => { // 复制一份
return fn;
});
this.next(params)
}
}
export default Middleware
const middleware = new Middleware()
function transform(options, next) {
console.log('before', options.data);
options.data.age = Number(options.data.age)
next(options); // 通过验证
}
function validate(options, next) {
console.log('validate', options.data);
next(options); // 通过验证
}
function send(options, next) {
setTimeout(function () { // 模拟异步
console.log('send', options.data);
next();
}, 100);
}
middleware.use(transform).use(validate).use(send)
middleware.execute({ data: { name: 'cookie', age: '20' } });
如上我们在发送请求之前加入了类型转换、数据校验,将数据的业务处理使用中间件模式剥离,使得处理过程模块化,维护性提升。
/**
* 注册事件
* @param {String} name 事件名称
* @param {Function (params)} callback 回调函数
*/
on(name, callback) {
if (typeof callback === 'function') {
this.$events[name] = callback
} else {
throw '事件回调必须为函数'
}
}
/**
* 发射(触发)事件
* @param {String} name 事件名称
* @param {Any} params 回调参数
*/
emit(name, params) {
if (this.$events[name]) {
let callback = this.$events[name]
callback.call(this, params)
} else {
throw '没有注册这个事件'
}
}
每个中间件的过程都是不可控制的,全部都交由中间类去统一调用,我们可以加入事件回调,方便我们在中间件处理过程中拥有额外的逻辑能力
将上述的使用方法再改造一下,方便实际业务中使用
function send(options, next) {
this.emit('request', options)
setTimeout(() => { // 模拟异步
console.log('send', options.data);
this.emit('response', options)
options.promise.resolve({ data: options.data })
}, 100);
}
// 请求之前的回调函数
middleware.on('request', params => {
// 在这里可以做请求之前的一些处理,比如添加全局参数等
console.log(params, '再多做一些处理')
})
// 请求成功的回调函数
middleware.on('response', params => {
// 在这里可以做下请求成功的一些处理,比如全局loading什么的
console.log(params, '请求成功')
})
middleware.use(transform).use(validate).use(send)
middleware.executeFc({ data: { name: 'cookie', age: '20' } }).then(({ data }) => {
console.log('finally', data)
});
上述的项目实例是采用 ajax 来演示,实际通用的中间件类,可以在业务中将一些流程化执行的任务拆分出来,例如表单验证、多重条件判断等等
const middleware = new Middleware()
function judge1(options, next) { // 空数校验
if (!options.data) {
options.promise.reject({ data: false, msg: '数据为空' })
return
}
next(options); // 通过验证
}
function judge2(options, next) { // 判断小于10
if (options.data < 10) {
options.promise.reject({ data: false, msg: '数据小于10' })
return
}
next(options); // 通过验证
}
function judge3(options, next) { // 判断大于30
if (options.data < 30) {
options.promise.reject({ data: false, msg: '数据小于30' })
return
}
options.promise.resolve({ data: true, msg: '数据小于30' })
}
middleware.use(judge1).use(judge2).use(judge3)
middleware.executeFc({ data: 40 }).then(({ data }) => {
console.log('finally', data)
}).catch(({ msg }) => {
console.log(msg)
})
将流程化执行的多种条件判断通过中间件解耦,可以使得条件判断方法更加清晰。一般当你需要使用中介者来改造业务逻辑的时候,前端的项目确实有点复杂了。
完整的 demo 地址:项目实战 demo,喜欢的朋友可以 star 一下,后续会根据设计模式博文的推出,逐步的将此项目继续拓展出来。