为什么要提高代码扩展性
我们写的代码都是为了一定的需求服务的,但是这些需求并不是一成不变的,当需求变更了,如果我们代码的扩展性很好,我们可能只需要简单的添加或者删除模块就行了,如果扩展性不好,可能所有代码都需要重写,那就是一场灾难了,所以提高代码的扩展性是势在必行的。怎样才算有好的扩展性呢?好的扩展性应该具备以下特征:
需求变更时,代码不需要重写。
局部代码的修改不会引起大规模的改动。有时候我们去重构一小块代码,但是发现他跟其他代码都是杂糅在一起的,里面各种耦合,一件事情拆在几个地方做,要想改这一小块必须要改很多其他代码。那说明这些代码的耦合太高,扩展性不强。
可以很方便的引入新功能和新模块。
怎么提高代码扩展性?当然是从优秀的代码身上学习了,本文会深入Axios,Node.js,Vue等优秀框架,从他们源码总结几种设计模式出来,然后再用这些设计模式尝试解决下工作中遇到的问题。本文主要会讲职责链模式,观察者模式,适配器模式,装饰器模式。下面一起来看下吧:
职责链模式职责链模式顾名思义就是一个链条,这个链条上串联了很多的职责,一个事件过来,可以被链条上的职责依次处理。他的好处是链条上的各个职责,只需要关心自己的事情就行了,不需要知道自己的上一步是什么,下一步是什么,跟上下的职责都不耦合,这样当上下职责变化了,自己也不受影响,往链条上添加或者减少职责也非常方便。
实例:Axios拦截器用过Axios的朋友应该知道,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 }) }上面的代码其实就完成了拦截器创建和use的逻辑,并不复杂,那这些拦截器方法都是什么时候执行呢?当然是我们调用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; }上述代码是从Axios源码中精简出来的,可以看出他巧妙的运用了职责链模式,将需要做的任务组织成一个链条,这个链条上的任务相互不影响,拦截器可有可无,而且可以有多个,兼容性非常强。
实例:职责链组织表单验证看了优秀框架对职责链模式的运用,我们再看看在我们平时工作中这个模式怎么运用起来。现在假设有这样一个需求是做一个表单验证,这个验证需要前端先对格式等内容进行校验,然后API发给后端进行合法性校验。我们先分析下这个需求,前端校验是同步的,后端验证是异步的,整个流程是同步异步交织的,为了能兼容这种情况,我们的每个验证方法的返回值都需要包装成promise才行
// 前端验证先写个方法 function frontEndValidator(inputValue) { return Promise.resolve(inputValue); // 注意返回值是个promise } // 后端验证也写个方法 function backEndValidator(inputValue) { return Promise.resolve(inputValue); } // 写一个验证器 function validator(inputValue) { // 仿照Axios,将各个步骤放入一个数组 const validators = [frontEndValidator, backEndValidator]; // 前面Axios是循环调用promise.then来执行的职责链,我们这里换个方式,用async来执行下 async function runValidate() { let result = inputValue; while(validators.length) { result = await validators.shift()(result); } return result; } // 执行runValidate,注意返回值也是一个promise runValidate().then((res) => {console.log(res)}); } // 上述代码已经可以执行了,只是我们没有具体的校验逻辑,输入值会原封不动的返回 validator(123); // 输出: 123