小程序单例模式实践

2,661 阅读4分钟

应用场景

目前我们的小程序【水羊潮妆官方商城】中,只允许单用户登录,且不能切换用户。用户的openId,token,用户名等个人信息,使用wx.setStorage()方法进行保存。调用时,使用wx.getStorage()方法获取。每个更新,获取用户信息的地方,是界面的负责人通过手写调用接口获取信息。如下:

改造前的调用

async getInfo() {
    const data = await getPesonalCenterInfo();
    if (data.success && data.data) {
      const { creditInfoVO = {} } = data.data;
      this.setData({
        'featureData.memberInfo': data.data
      });
      wx.setStorageSync('creditName', creditInfoVO.creditRuleName);
    } else {
      const isLogined = isLogin();
      if (data.code === '400' && isLogined) {
        wx.removeStorageSync('userToken');
        if (isLogined) {
          jump.toPage('5');
        }
      }
    }
  },

此方法存在几个问题:

  • 重复的copy paste,大段代码相同,浪费开发时间
  • 没有统一的调用处,后续需要对每次登录登出做日志记录,需要在每个地方进行添加,维护成本大
  • 速度慢。目前来看wx.getStorage()/wx.setStorage()等方法,对磁盘进行了存取,而不是对内存进行存取。速度会慢20多倍。
  • 请求存在浪费。例如每个页面调用用户信息接口时,并不会关心之前是否调用过,目前是否正在调用,还未返回。所以在整个APP里,会存在很短时间(2s)内,多次调用的情况。但是用户信息不会真的更新这么快。接口调用存在不必要,浪费网络资源和后台资源,阻塞客户端其他的接口(微信对请求的并发数有限制)。

目前市面上最好的SSD的存取速度也要比内存慢20多倍。

改造

class UserInfo {
  static fetchBasicInfoTime = null;
  static intervalOfFetch = 20000;
  /**
   *Creates an instance of UserInfo.
   * @memberof UserInfo
   */
  constructor() {
    
  }
  /**
   * 获取单例
   */
  static getInstance() {
    if (!UserInfo.instance) {
      UserInfo.instance = new UserInfo();
    }
    return UserInfo.instance;
  }
  /**
   * 获取昵称 头像
   * ignoreCache 是否忽略缓存,默认false
   * @memberof UserInfo
   */
  async fetchBasicInfo(ignoreCache = false) {
    const now = Date.now();

    if (
      ignoreCache ||
      now - UserInfo.fetchBasicInfoTime > UserInfo.intervalOfFetch ||
      !this.basicInfo ||
      !UserInfo.fetchBasicInfoTime
    ) {
      UserInfo.fetchBasicInfoTime = now;
      const res = await getPesonalCenterInfo();
      if (res.success && res.data) {
        const basicInfo = { basicInfo: res.data };
        Object.assign(this, basicInfo);
        const { creditInfoVO = {} } = res.data;
        console.log('UserInfo', '===> fetchBasicInfo()', creditInfoVO);
        if (!creditInfoVO.creditRuleName) {
          creditInfoVO.creditRuleName = '余额';
        }
        wx.setStorageSync('creditName', creditInfoVO.creditRuleName);
      }
      return res;
    } else {
      const response = new Response();
      response.data = this.basicInfo;
      return response;
    }
  }
 }

改造后的调用

  async getInfo() {
   const data = await userInfo.getInstance().fetchBasicInfo();
   if (data.success && data.data) {
     const { creditInfoVO = {} } = data.data;
     this.setData({
       'featureData.memberInfo': data.data
     });
   } else {
     const isLogined = isLogin();
     if (data.code === '400' && isLogined) {
       await userInfo.logout();
       if (isLogined) {
         jump.toPage('5');
       }
     }
   }
 },

改造后的结果

  • 调用被集中起来,统一处理日志,错误。
  • 无需重复的copy paste,只需导入和调用方法。
  • 速度提升。如果结果存在,直接从内存出。
  • 增加忽略缓存的参数。默认值是false。在限定时间内,将会缓存直出,如果客户端明确知道,信息被修改或者需要忽略缓存,那么可以设置为false。减少了无用调用次数。

回顾一下单例模式

介绍

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

应用实例

1、一个班级只有一个班主任。
2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。 

优点

1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。 

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

使用场景

1、要求生产唯一序列号。
2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化