不知道怎么提⾼代码可扩展性?来看看优秀框架源码中的这⼏种设计模式吧!为什么要提⾼代码扩展性
我们写的代码都是为了⼀定的需求服务的,但是这些需求并不是⼀成不变的,当需求变更了,如果我们代码的扩展性很好,我们可能只需要简单的添加或者删除模块就⾏了,如果扩展性不好,可能所有代码都需要重写,那就是⼀场灾难了,所以提⾼代码的扩展性是势在必⾏的。怎样才算有好的扩展性呢?好的扩展性应该具备以下特征:
1. 需求变更时,代码不需要重写。
2. 局部代码的修改不会引起⼤规模的改动。有时候我们去重构⼀⼩块代码,但是发现他跟其他代码都是杂糅在⼀起的,⾥⾯各种耦合,⼀件事情拆在
⼏个地⽅做,要想改这⼀⼩块必须要改很多其他代码。那说明这些代码的耦合太⾼,扩展性不强。
3. 可以很⽅便的引⼊新功能和新模块。
怎么提⾼代码扩展性?
当然是从优秀的代码⾝上学习了,本⽂会深⼊Axios,Node.js,Vue等优秀框架,从他们源码总结⼏种设计模式出来,然后再⽤这些设计模式尝试解决下⼯作中遇到的问题。本⽂主要会讲职责链模式,观察者模式,适配器模式,装饰器模式。下⾯⼀起来看下吧:
职责链模式
职责链模式顾名思义就是⼀个链条,这个链条上串联了很多的职责,⼀个事件过来,可以被链条上的职责依次处理。他的好处是链条上的各个职责,只需要关⼼⾃⼰的事情就⾏了,不需要知道⾃⼰的上⼀步是什么,下⼀步是什么,跟上下的职责都不耦合,这样当上下职责变化了,⾃⼰也不受影响,往链条上添加或者减少职责也⾮常⽅便。
实例:Axios拦截器
⽤过Axios的朋友应该知道,Axios的拦截器有请求拦截器和响应拦截器,执⾏的顺序是请求拦截器 -> 发起请求 -> 响应拦截器,这其实就是⼀个链条上串起了三个职责。下⾯我们来看看这个链条怎么实现:
// 先从⽤法⼊⼿,⼀般我们添加拦截器是这样写的
// quest.u(fulfilled, rejected)
// 根据这个⽤法我们先写⼀个Axios类。
function Axios(){
// 实例上有个interceptors对象,⾥⾯有request和respon两个属性
// 这两个属性都是InterceptorManager的实例
this.interceptors ={
request:new InterceptorManager(),
respon:new InterceptorManager()
};
}
// 然后是实现InterceptorManager类
function InterceptorManager(){
// 实例上有⼀个数组,存储拦截器⽅法
this.handlers =[];
}
// InterceptorManager有⼀个实例⽅法u
InterceptorManager.prototype.u=function(fulfilled, rejected){
// 这个⽅法很简单,把传⼊的回调放到handlers⾥⾯就⾏
this.handlers.push({
fulfilled,
rejected
})
}
上⾯的代码其实就完成了拦截器创建和u的逻辑,并不复杂,那这些拦截器⽅法都是什么时候执⾏呢?当然是我们调⽤quest的时候,调⽤quest的时候真正执⾏的就是请求拦截器 -> 发起请求 -> 响应拦截器链条,所以我们还需要来实现
下quest:
quest=function(config){
// chain⾥⾯存的就是我们要执⾏的⽅法链条
// dispatchRequest是发起⽹络请求的⽅法,本⽂主要讲设计模式,这个⽅法就不实现了
// chain⾥⾯先把发起⽹络请求的⽅法放进去,他的位置应该在chain的中间
const chain =[dispatchRequest, undefined];
// chain前⾯是请求拦截器的⽅法,从request.handlers⾥⾯取出来放进去
quest.handlers.forEach(function unshiftRequestInterceptors(interceptor){
chain.unshift(interceptor.fulfilled, jected);著名数学家
});
// chain后⾯是响应拦截器的⽅法,从respon.handlers⾥⾯取出来放进去
spon.handlers.forEach(function pushResponInterceptors(interceptor){
chain.push(interceptor.fulfilled, jected);
});
// 经过上述代码的组织,chain这时候是这样的:
// [request.fulfilled, jected, dispatchRequest, undefined, respon.fulfilled,
// jected]
// 这其实已经按照请求拦截器 -> 发起请求 -> 响应拦截器的顺序排好了,拿来执⾏就⾏
let promi = solve(config);// 先来个空的promi,好开启then
while(chain.length){
// ⽤promi.then进⾏链式调⽤
promi = promi.then(chain.shift(), chain.shift());
}
return promi;
}
上述代码是从中精简出来的,可以看出他巧妙的运⽤了职责链模式,将需要做的任务组织成⼀个链条,这个链条上的任务相互不影响,拦截器可有可⽆,⽽且可以有多个,兼容性⾮常强。
实例:职责链组织表单验证
看了优秀框架对职责链模式的运⽤,我们再看看在我们平时⼯作中这个模式怎么运⽤起来。现在假设有这样⼀个需求是做⼀个表单验证,这个验证需要前端先对格式等内容进⾏校验,然后API发给后端进⾏合法性校验。我们先分析下这个需求,前端校验是同步的,后端验证是异步的,整个流程是同步异步交织的,为了能兼容这种情况,我们的每个验证⽅法的返回值都需要包装成promi才⾏
// 前端验证先写个⽅法
function frontEndValidator(inputValue){
solve(inputValue);// 注意返回值是个promi
}
// 后端验证也写个⽅法
function backEndValidator(inputValue){
solve(inputValue);
}
// 写⼀个验证器
function validator(inputValue){
// 仿照Axios,将各个步骤放⼊⼀个数组
const validators =[frontEndValidator, backEndValidator];
何以战// 前⾯Axios是循环调⽤promi.then来执⾏的职责链,我们这⾥换个⽅式,⽤async来执⾏下
async function runValidate(){
let result = inputValue;
while(validators.length){
result =await validators.shift()(result);
体股癣是什么
}
return result;
}
// 执⾏runValidate,注意返回值也是⼀个promi
runValidate().then((res)=>{console.log(res)});
}
市场营销渠道// 上述代码已经可以执⾏了,只是我们没有具体的校验逻辑,输⼊值会原封不动的返回
validator(123);// 输出: 123
上述代码我们⽤职责链模式组织了多个校验逻辑,这⼏个校验之间相互之间没有依赖,如果以后需要减少某个校验,只需要将它
从validators数组中删除即可,如果要添加就往这个数组添加就⾏了。这⼏个校验器之间的耦合度就⼤⼤降低了,⽽且他们封装的是promi,完全还可以⽤到其他模块去,其他模块根据需要组织⾃⼰的职责链就⾏了。
观察者模式
观察者模式还有个名字叫发布订阅模式,这在JS的世界⾥可是⼤名⿍⿍,⼤家或多或少都⽤到过,最常见的就是事件绑定了,有些⾯试还会要求⾯试者⼿写⼀个事件中⼼,其实就是⼀个观察者模式。观察者模式的优点是可以让事件的产⽣者和消费者相互不知道,只需要产⽣和消费相应的事件就⾏,特别适合事件的⽣产者和消费者不⽅便直接调⽤的情况,⽐如异步中。我们来⼿写⼀个观察者模式看看:
class PubSub {
constructor(){
// ⼀个对象存放所有的消息订阅
// 每个消息对应⼀个数组,数组结构如下
// {
// "event1": [cb1, cb2]
// }
this.events ={}
}
subscribe(event, callback){
if(this.events[event]){
// 如果有⼈订阅过了,这个键已经存在,就往⾥⾯加就好了
this.events[event].push(callback);
}el{
/
淋失
/ 没⼈订阅过,就建⼀个数组,回调放进去
this.events[event]=[callback]
}
}
publish(event,...args){
// 取出所有订阅者的回调执⾏
const subscribedEvents =this.events[event];
if(subscribedEvents && subscribedEvents.length){
subscribedEvents.forEach(callback =>{
callback.call(this,...args);
});
}
}
unsubscribe(event, callback){
// 删除某个订阅,保留其他订阅
const subscribedEvents =this.events[event];
if(subscribedEvents && subscribedEvents.length){
this.events[event]=this.events[event].filter(cb => cb !== callback)
}
}
}
// 使⽤的时候
const pubSub =new PubSub();
pubSub.subscribe('event1',()=>{});// 注册事件
pubSub.publish('event1');// 发布事件
实例:Node.js的EventEmitter
观察者模式的⼀个典型应⽤就是Node.js的EventEmitter,我有另⼀篇⽂章从异步应⽤的⾓度详细讲解了观察者模式的原理和Node.js的EventEmitter源码,我这⾥就不重复书写了,上⾯的⼿写代码也是来⾃这篇⽂章。
实例:转圈抽奖
⼀样的,看了优秀框架的源码,我们⾃⼰也要试着来⽤⼀下,这⾥的例⼦是转圈抽奖。想必很多朋友都在⽹上抽过奖,⼀个转盘,⾥⾯各种奖品,点⼀下抽奖,然后指针开始旋转,最后会停留到⼀个奖品那⾥。我们这个例⼦就是要实现这样⼀个Demo,但是还有⼀个要求是每转⼀圈速度就加快⼀点。我们来分析下这个需求:
1. 要转盘抽奖,我们肯定先要把转盘画出来。
2. 抽奖肯定会有个结果,有奖还是没奖,具体是什么奖品,⼀般这个结果都是API返回的,很多实现⽅案是点击抽奖就发起API请求拿到结果了,转
圈动画只是个效果⽽已。
3. 我们写⼀点代码让转盘动起来,需要⼀个运动效果
4. 每转⼀圈我们需要加快速度,所以还需要控制运动的速度
通过上⾯的分析我们发现⼀个问题,转盘运动是需要⼀些时间的,当他运动完了需要告诉控制转盘的模块加快速度进⾏下⼀圈的运动,所以运动模块和控制模块需要⼀个异步通信,这种异步通信就需要我们的观察者模式来解决了。最终效果如下,由于只是个DEMO,我就⽤⼏个DIV块来代替转盘了:
下⾯是代码:
// 先把之前的发布订阅模式拿过来
class PubSub {
constructor(){
this.events ={}
}
subscribe(event, callback){
if(this.events[event]){怎样选平板电脑
this.events[event].push(callback);
}el{
this.events[event]=[callback]
}
}
publish(event,...args){
const subscribedEvents =this.events[event];
宾语补足语if(subscribedEvents && subscribedEvents.length){
subscribedEvents.forEach(callback =>{
callback.call(this,...args);
});
}
}
unsubscribe(event, callback){
const subscribedEvents =this.events[event];
if(subscribedEvents && subscribedEvents.length){
this.events[event]=this.events[event].filter(cb => cb !== callback)陶虹个人资料简介
}
}
}
// 实例化⼀个事件中⼼
const pubSub =new PubSub();
// 总共有初始化页⾯ -> 获取最终结果 -> 运动效果 -> 运动控制四个模块
// 初始化页⾯
const domArr =[];
function initHTML(target){
// 总共10个可选奖品,也就是10个DIV
for(let i =0; i <10; i++){
let div = ateElement('div');
div.innerHTML = i;
div.tAttribute('class','item');
target.appendChild(div);