阅读 731

手摸手教你在前端开发中使用设计模式

前言

提到设计模式可能大家会感觉有点高大上的感觉,其实这个东西的概念很抽象,但是在实际开发中还是有很多地方可以使用设计模式的思想,对我们的代码进行一次优化。本文旨在介绍一些开发中常用的设计模式,通过例子来进行学习,尽量对概念一带而过。为了讨隔壁前端小姐姐的崇拜的目光,一起学一下吧!

image.png

正文

1. 适配器模式

概念:将一个类的接口转化成另外一个接口,以满足用户需求,使类(对象)之间的接口的不兼容问题通过适配器得以解决。

在工作中,有时为了满足需求,在开发新模块的时候需要对老模块做一个兼容,这时候就需要适配器模式。

例如

  1. 方法默认传参
// 参数大于2建议使用对象进行合并
function doSomeThing(obj){
  const adapter = {
    name:"默认姓名",
    color:"red",
    size:200,
    age:20
  }
  for (let i in adapter){
    adapter[i] = obj[i] || adapter[i];
  }
  // do some thing
}
复制代码

可以看到这么处理之后我们无论传入的参数是什么都可以保证对象中拥有这几个属性。避免后续逻辑出错。

  1. 全局封装方法的扩展与兼容。

在vue使用中,我们通常会将api请求挂载在vue实例上,但是有些老的项目是通过封装ajax请求,在对老项目进行重构时需要将所有的api请求放到axios上或者fetch上。这就可以使用适配器模式。

// 对fetch 封装伪代码
export default class FetchApi{
    static get(url){
		return new Promise((resolve,reject)=>{
      fetch(url).then(...).catch(err=>reject(err))
    })
	}
  static post(url,data){
    return new Promise(...)
  }
}
  
// 在使用时我们可以这么使用
  const res = await FetchApi.get(url) || {};
// 或者
  const res = await FetchApi.post(url,params) || {}
  
  
// 再看一下原有的接口封装方式  伪代码
  function Ajax(type,url,data){
    const xhr = new XMLHttpRequest()
    ...
    if(type === 'GET'){
      xhr.open('GET',url+"?"+data,true);
      xhr.send()
    }else if(type === 'POST'){
        xhr.open('POST',url,true);
        xhr.setRequestHeader("Content-type",'application/x-www-form-urlencoded')
    	xhr.send(data)
    }
    ...
  }
  
  // 调用方式
  Ajax("GET",url,data)
  
复制代码

在这里我们可以看到新老接口的接口名不一样,请求传参也不一样。没关系,使用适配器解决。

 // 伪代码  
  async function ajaxAdapterr(type,url,data){
    let result
    if(type === 'GET'){
      result = await FetchApi.get(url) || {}
    }else if(type === 'POST'){
      result = await FetchApi.post(url,data) || {}
    }
  }
    
   async function Ajax(type,url,data){
        await ajaxAdapterr(type,url,data);
   }
复制代码

这样就可以对原有的ajax请求变更到fetch上,避免一个个去修改页面中的请求。节省大量了时间可以用来摸鱼。

image.png

2. 策略模式

概念:将定义的一组算法封装起来,使其相互之间可以替换。封装的算法具有一定的独立性,不会随客户端的变化而变化。

概念这东西就是善于将人人都懂的东西描述成人人都不懂。我们还是看实际场景吧。

工作中,相信大家一定写过很多很多的if else判断。当条件越来越多的时候,这种书写方式就会变得特别臃肿,那么就用策略模式优化一下吧。

例如

function doSomeThing(type){
  if(type === 'pre'){
    return 100
  }else if(type === 'onSale'){
    return 200
  }else if(type === 'back'){
    return 150
  }else if(type === 'fresh'){
    return 250
  }
}

// 逻辑会越来越臃肿 用策略模式优化一下
function doSomeThing(type){
  const priceType = {
    pre(){
      return 100
    },
    onSale(){
      return 200
    },
    back(){
      return 150
    },
    fresh(){
      return 250
    }
  }
  return priceType[type]()
}
复制代码

可以看到,在使用策略模式优化之后,代码的映射关系很明确,并且更加的灵活直观,后期的维护只需要去在对象中添加方法即可。帅就一个字,我只说一次。这样的代码哪个前端小姐姐看了不说棒呢?

3. 状态模式

概念:当一个对象的内部状态发生改变时,会导致其行为的改变,这看起来像是改变了对象。

状态模式的概念与策略模式类似,都是封装行为、都通过委托来实现行为分发。但是策略模式里面分发的方法,没有依赖,互相平行,进水不犯河水。而状态模式里,各函数与主体存在一定的关联。

例如

自动咖啡机

class Coffee{
  constructor(){
    this.state ='黑咖啡';
  }
  stateProcessor = {
      american: () => {
         console.log("黑咖啡")
      },
      latte: () => {
          this.stateProcessor.american(); // 制作黑咖啡
          console.log("加奶")
      },
      vanillaLatte: () => {
           this.stateProcessor.latte();
           console.log("加香草🌿糖浆")
      },
      mocha: () => {
            this.stateProcessor.latte();
            console.log('加巧克力🍫')
      }
  }
 
  changeState(state){
    this.state = state;
    if(!this.stateProcessor[state]){
      return console.log("暂无此咖啡")
    }
    this.stateProcessor[state]()
  }
}

const coffee = new Coffee();
coffee.changeState("latte")
复制代码

状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况,把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。

4. 代理模式

概念:由于一个对象不能直接引用另一个对象,所以需要通过代理对象在这两个对象之间起到中介作用。

代理大家可能平常开发中都有涉及到,基本那种代理这里不做介绍,这里介绍一下缓存代理

例如: 对入参进行求和处理

// addAll方法会对你传入的所有参数做求和操作
const addAll = function() {
    console.log('进行了一次新计算')
    let result = 0
    const len = arguments.length
    for(let i = 0; i < len; i++) {
        result += arguments[i]
    }
    return result
}

// 为求和方法创建代理
const proxyAddAll = (function(){
    // 求和结果的缓存池
    const resultCache = {}
    return function() {
        // 将入参转化为一个唯一的入参字符串
        const args = Array.prototype.join.call(arguments, ',')
        // 检查本次入参是否有对应的计算结果
        if(args in resultCache) {
            // 如果有,则返回缓存池里现成的结果
            return resultCache[args]
        }
        return resultCache[args] = addAll(...arguments)
    }
})()
复制代码

image-20210719122848035.png

可以看到,在入参一致的情况下,只做了一次计算,后续都是从缓存中取值进行返回,这在计算量特别大的情况下将会极大节省时间开销。

5. 装饰者模式

概念: 在不改变原对象的基础上,通过对其进行包装扩展(添加属性或者方法),使得原有对象可以满足更复杂的需求。

例如: 在审批流中,原需求是审批通过拒绝都放在了一起进行了处理,后期增加了需求,在拒绝时需要显示一个弹框输入拒绝原因

// 原有的审批逻辑
function approvalOrReject(){
	// do some thing
}

function rejectShowBox(){
	this.approvalOrReject();
	this.showMessageBox(); // 显示弹框
}
复制代码

如此一来,我们就实现了“只添加,不修改”的装饰器模式。实际开发中的装饰器模式用处特别多,大家可以用心去发现~

小结

暂时先总结这些设计模式吧,后续还会继续整理添加,请大家点赞收藏关注咯~

自从用了设计模式,小姐姐都开始对我频频点头了,兄弟们,用起来吧。

由于本人水平有限,文内如有错误欢迎与我联系。

文章分类
前端