应用场景
目前我们的小程序【水羊潮妆官方商城】中,只允许单用户登录,且不能切换用户。用户的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 被多次实例化