2、设计模式创建型之单例模式

设计模式创建型之单例模式

 

1.单例模式介绍

 
由于单例模式比较简单,我们可以首先给出它的定义:一个类仅有一个实例,并提供一个访问它的全局访问点。

一个类仅有一个实例?那么我们在什么情况下需要使用它呢,在前端又有什么应用场景?
 

我们可以先举一些例子:

1、由于 Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,因此文件的处理必须通过唯一的实例来进行。

2、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

 
当你想控制实例的数量时,可以考虑单例模式。

现在请考虑这样一个情况,你有一个全局使用的类频繁地创建与销毁。如果大量使用该类则难免造成系统资源的浪费。可以通过创建一个全局可使用的实例来避免此问题出现。

大致了解什么是单例模式后,让我们看看在前端应该怎么实现吧。
 

如果要全局使用一个实例,那就需要该类具备判断全局范围是否已经创建过实例的能力。像下面这样就是不行的:

class SinglePeople {
  constructor(name) {
    this.name = name;
  }
  eat = () => {
    console.log(`${this.name}可以吃东西!`);
  }
}

const person1 = new SinglePeople("张三");
const person2 = new SinglePeople("张三");

console.log(person1 === person2); // false

 
可以看到,虽然我们本意是想创建张三这个人,但是通过 SinglePeople 创建的实例却是不一样的,由此表明这样的创建方式是无法实现单例模式的。
 
因此我们给类增加一个属性 instance ,用于判断是否已经存在实例,就像这样:

class SinglePeople {
  constructor(name) {
    this.name = name;
  }

  static getInstance(name) {
    // 判断是否已经创建过实例
    if (!SinglePeople.instance) {
        // 实例不存在则创建
        SinglePeople.instance = new SinglePeople(name)
    }
    // 实例存在则直接返回
    return SinglePeople.instance;
  }

  eat = () => {
    console.log(`${this.name}可以吃东西!`);
  }
}

 
现在让我们来试验一下:

const person1 = SinglePeople.getInstance("张三");
const person2 = SinglePeople.getInstance("张三");

console.log(person1 === person2); // true

 
可以看到,通过这样方式是可以实例单例模式的。

关于单例模式在前端中典型应用的代表,那么就不得不提到 Redux 和 Vuex 中的 State。我们这里仅用 Redux 来举例。

 

2.单例模式的应用之 Redux

 
单一数据源:整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。

上面这段直接引自于官网关于对单一数据源的介绍。

在 react 中,父子组件间通信一般会使用 props ,或者通过 context 隔层传递数据。兄弟组件也可以通过状态提升的方式来共享数据。

但当组件非常多、组件间关系复杂、且嵌套层级很深的时候,这种原始的通信方式会使我们的逻辑变得复杂难以维护,此时就可以考虑使用 Redux 来保存数据。而 Store 就是用于存放数据的唯一数据源。

关于 Redux 详细的内容,大家可以自行选择参考 Redux 官网或者翻阅相关源码,这里提到 Redux 仅为了提供一个大家平常接触较多的例子。如果对 Redux 不了解也不影响,只需要牢记:整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。这是单例模式的典型应用场景。

 

3.实现一个 Storage 存储

 
本小节将会实现一个基于 localStorage 全局的的存储类。
 

class MyStorage {
  
  // 核心部分,实例不存在则创建,已创建则直接返回
  static getInstance = () => {
    if (!MyStorage.instance) {
      MyStorage.instance = new MyStorage();
    }
    return MyStorage.instance;
  }

  setState(key, value) {
    window.localStorage.setItem(key, value)
  }

  getState(key) {
    return window.localStorage.getItem(key);
  }

  // 测试函数
  show = () => {
    console.log(window.localStorage);
  }

}

 
可以看到,上面代码的核心部分不变,仍然是 getInstance 这个静态函数。

下面可以测试一下:

// 创建一个实例
const store1 = MyStorage.getInstance();
store1.show(); // Storage {length: 0}

store1.setState("name", "张三");
store1.setState("age", 24);
store1.setState("sex", "男");
store1.show(); // Storage {age: '24', name: '张三', sex: '男', length: 3}
console.log(store1.getState(("name"))); // 张三

// 再创建一个实例,判断是否会重复创建
const store2 = MyStorage.getInstance();
console.log(store1 === store2); // true
store2.show(); // Storage {age: '24', name: '张三', sex: '男', length: 3}

// 为 store2 中放入新值,观察 store1 的数据
store2.setState("work", "程序员")
store1.show(); // Storage {work: '程序员', name: '张三', age: '24', sex: '男', length: 4}
store2.show(); // Storage {work: '程序员', name: '张三', age: '24', sex: '男', length: 4}

可以看到,上面真正实现了 store1 和 store2 的数据共享。
 

现在来小结一下:在单例模式中,只需要牢记两点:
1、一个类仅有一个实例,并提供一个访问它的全局访问点。
2、并且理解这段代码:

static getInstance = () => {
  if (!MyStorage.instance) {
    MyStorage.instance = new MyStorage();
  }
  return MyStorage.instance;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值