首页 > 其他分享 >设计模式创建型之单例模式

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

时间:2024-02-23 18:14:14浏览次数:26  
标签:创建 模式 实例 单例 Vuex 设计模式 MyStorage

实验介绍

本实验主要介绍了设计模式中的单例模式,在前端领域中,有很多地方都运用到了单例模式的思维,例如目前的主流前端框架中所用到的 Redux 和 Vuex 。实验首先通过一个小例子为大家展示了单例模式的实现原理,随后通过完成一个自定义的 Storage 存储器来帮助大家加深对单例模式的理解。最后为大家简单介绍了 Vuex 的基本原理,展示了单例模式的真实应用场景。希望大家在本小节实验中有所收获。

知识点

  • 单例模式介绍
  • 如何实现一个 Storage 存储器
  • 单例模式的应用之 Vuex 和 Redux
  • 实验挑战

单例模式介绍

一个类仅有一个实例?没错,当你想控制实例的数量时,可以考虑单例模式。

我们可以先举两个例子:

  • Windows 是多进程多线程的,但是在操作一个文件的时候,必然只能让一个进程(线程)来控制,否则会出现同一个文件被多个进程(线程)修改,导致文件修改异常的情况。
  • windows 的回收站是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例,试想一下,如果回收站不是唯一的,那你删除的文件就可能很难被复原了,因为你很有可能不知道文件被放在了哪个回收站。

在代码实现过程中,当你有一个类被频繁地创建与销毁时,这难免会造成系统资源的浪费。

或者是你对某个类实例化后,对这个实例对象进行了一些操作,例如存入了大量数据。而此时在其他地方你也需要使用这个数据,然而你又不能再实例化一个,因为新实例化的是没有之前实例对象中的数据的。

这种情况下你只能想办法在全局使用同一个实例对象。到后面你会发现这实际上就是 Vuex 和 Redux 的实现原理,后面会详细讲这一部分。

因此我们需要明确的一点是:如果要实现单例模式,那么创建实例的类就要具备判断全局范围是否已经创建过实例的能力,像下面这样就是不行的:

在实验环境中新建 index.html 文件与 singlePeople1.js 文件,并在 index.html 中引入 singlePeople1.js

随后在 singlePeople1.js 中添加如下代码:

// singlePeople1.js

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 ,用于判断是否已经存在实例,就像这样:

创建 singlePeople2.js ,并在 index.html 中引入:

// singlePeople2.js

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}可以吃东西!`);
  };
}

现在让我们来试验一下:

// singlePeople2.js

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

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

可以看到,即使我们创建了两次实例,然而两次的实例却是一样的,于是可以知道通过这样方式是可以实例单例模式的。

至此,通过这个例子,相信各位同学应该能对单例模式有一个大体的概念,即一个类仅有一个实例,并提供一个访问它的全局访问点,即使重复的通过该类创建实例,也只会返回已经被创建过的实例。

接下来,我们会通过实现一个基于 localStorage 全局的的存储器,来看一看在前端中如何应用单例模式。

实现一个 Storage 存储器

在前端的项目中,有一些数据是全局任何地方都有可能用到的,因此需要把这样的数据以某种方式保存起来,并且支持在任何文件中都能调用到。

在本小节中,我们将通过 localStorage 来实现一个全局的 Storage 存储器,它运用单例模式的思维,提供了唯一的全局访问点,可以读取数据和存放数据,以便于一些常用的数据能被方便的调用到。

先创建一个 myStorage.js 文件,并引入 index.html 中。

首先我们需要构建一个 MyStorage 类,其核心之处在于,需要该类具有这样的能力:实例不存在则创建,已创建则直接返回已创建过的实例。

就像这样:

// myStorage.js

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

同样的,我们来测试一下是否能够满足上面的要求:

// myStorage.js

const storage1 = MyStorage.getInstance();
const storage2 = MyStorage.getInstance();

console.log(storage1 === storage2); // true

可以看到,该 MyStorage 类的确能提供唯一的全局访问点。

但此时还未结束,我们还需要为它添加读取数据和保存数据的能力,于是为 MyStorage 添加 setStategetState 方法:

// myStorage.js

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

  // 保存数据:通过 key/value 的形式传参
  setState(key, value) {
    window.localStorage.setItem(key, value);
  }

  // 通过 key 读取数据
  getState(key) {
    return window.localStorage.getItem(key);
  }

  // 为了方便查看数据,添加展示全部 localStorage 中数据的函数
  show = () => {
    console.log(window.localStorage);
  };
}

可以看到,setStategetState 方法本质也是通过调用 localStorage 的内置 API 来实现的,但这的确提供了我们所需要的能力。

接下来就让我们来测试一下这个 MyStorage 类是否能满足我们的需求:

第一步:创建一个实例,并查看当前存储情况

// myStorage.js

const store1 = MyStorage.getInstance();
store1.show(); // Storage {length: 0}

可以看到,该存储器暂时没有存放任何数据。

第二步:测试添加数据和读取数据的能力

// myStorage.js

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

通过打印输出,可以看到该实例的添加数据和读取数据功能都没有问题。

第三步:再创建一个实例,判断是否会重复创建

// myStorage.js

const store2 = MyStorage.getInstance();
console.log(store1 === store2); // true
store2.show(); // Storage {age: '24', name: '张三', sex: '男', length: 3}

从打印结果来看,store2 store1 实际上是同一个实例,其保存的数据是一样的

第四步:为 store2 中放入新值,观察 store1 的数据

// myStorage.js

store2.setState("work", "程序员");

store1.show(); // Storage {work: '程序员', name: '张三', age: '24', sex: '男', length: 4}
store2.show(); // Storage {work: '程序员', name: '张三', age: '24', sex: '男', length: 4}

从这一步的结果可以看到,store2 和 store1 打印结果相同,证明它们确实是同一个实例。

至此,这个简单的小例子就讲完了,相信同学们通过这里例子能更好的了解单例模式的定义与应用。

那么在当前的前端领域中,有哪些真正运用到单例模式的呢?

Redux 和 Vuex 相信大部分同学都有了解,或者了解其中的一种。它们实际上就是前端框架对单例模式的经典应用,如果你不了解也没有关系,在下一小节中,我们会对二者进行一个简单的介绍。

单例模式的应用之 Vuex 和 Redux

谈起 Rudex 和 Vuex ,首先要讲一讲它们的由来。

虽然这二者之间有差别,但在单例模式的应用层面是一致的,考虑到国内使用 Vue 的同学偏多,接下来就以 Vuex 为例( Redux 类似 )。

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。(摘自 vuex 官网)

对于没有实际使用过 Vuex 的同学而言,上面一句话并不是很容易理解,我们先来看看它能做什么。

在 Vue 开发过程中,对于某个状态,或者说某个值需要被很多组件用到时,一般会面对两个问题:

  1. 多个视图依赖于同一状态;
  2. 来自不同视图的行为需要变更同一状态。

对于第一个问题,当你的项目足够简单时,你完全可以采用组件的逐层传参,然而当项目复杂起来,你会发现这样的方式非常不方便,当你修改其中某一环组件,可能会影响整个传参路线。同时不能处理兄弟间状态传递,只能处理父子间状态传递。

对于第二个问题,同样是当项目复杂起来时会让代码维护和修改变得异常艰难,且同样无法处理兄弟间的状态。尽管可以通过状态提升的方式,但这样无疑会导致嵌套更多的组件。

这里对状态提升做个简单的解释:当你需要在兄弟间组件中使用同一状态时,你可以将此状态放在他们的父组件中,然后传进需要的子组件;当你需要修改时,也只能通过在子组件中调用父组件中修改状态的方法。

回想我们对单例模式的定义:一个类仅有一个实例,并提供一个访问它的全局访问点。

而 Vuex 正是使用了这样的思想:它把组件的共享状态抽取出来,以一个全局单例模式进行管理。在这种模式下,组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!

这里为大家抽取了一下源码中关于单例模式的关键代码:

let Vue;
// ...

export function install(_Vue) {
  // 这里就是和我们上面创建单例的思路一样
  // 首先判断是否已经创建过了唯一的 vuex 实例,创建过则直接返回
  if (Vue && _Vue === Vue) {
    if (process.env.NODE_ENV !== "production") {
      console.error(
        "[vuex] already installed. Vue.use(Vuex) should be called only once."
      );
    }
    return;
  }
  // 若没有,则为这个创建一个唯一的 Vuex
  Vue = _Vue;
  // 将 Vuex 的初始化逻辑写进Vue的钩子函数里
  applyMixin(Vue);
}

你可以清晰的看到,虽然具体的代码实现不同,但是其本质是一样的:若已经存在类的实例则不再重新创建,不存在时才创建一个新的实例。

注:如果你仍然对这一块不太清楚,希望可以去 Vuex 官网系统学习一下它,或者在报告中留言你不清楚的地方。( Vuex 官网

这里仅仅用 Vuex 进行举例,Redux 也是类似的。实际上,Vuex 在设计上借鉴了 Redux,二者的本质都在于,运用单例模式的思想,构建了一个全局单例模式的 object tree 中,以便于在各个层级的组件中使用。

这就是单例模式在前端领域中较为经典的应用场景。

实验挑战

利用单例模式的思想实现登录框的显示和隐藏。

提示:可以使用控制台打印的方式模拟登录框是在显示状态还是隐藏状态。

答案代码放在实验最后课程的源码包里,大家可以自行下载,和自己的实现方式进行比对。

小结及下节实验内容预告

至此,关于单例模式的介绍基本告一段落了,单例模式的定义相较于简单,希望大家能够认真学习,真正掌握。

小结一下:在单例模式中,需要牢记两点:

  1. 一个类仅有一个实例,并提供一个访问它的全局访问点。
  2. 理解这段代码:
static getInstance = () => {
  if (!MyStorage.instance) {
    MyStorage.instance = new MyStorage();
  }
  return MyStorage.instance;
}

下一节实验会为大家带来原型模式,并且会着重介绍原型及原型链,理解好原型不仅能帮助大家学习好原型模式,还能加深对 javascript 的理解。

原型与原型链不容易理解,下一节会尽全力为大家理清其概念,不过仍然希望各位能在学习下一节实验之前先自行在网上搜索一下相关内容,进行预习,这样在进行下一节实验的学习时能做到心中有数,也能更快、更好的理解。

实验总结

本节实验为大家展示了单例模式在前端领域的应用场景,我们说学习要知其然,更要知其所以然。以往大家在使用 Vuex 或 Redux 的过程中,也许并没有意识到这是单例模式的思想。通过本小节实验的学习帮助大家了解它们的实现原理,在以后的使用过程中,能够做到心中有数。

本节实验源码压缩包下载链接:单例模式源码

标签:创建,模式,实例,单例,Vuex,设计模式,MyStorage
From: https://www.cnblogs.com/xzemt/p/18030128

相关文章

  • 【Python&GIS】Python线矢量等距离取点/线等分取点&点创建矢量面
    ​        不多说,这是之前项目需求的代码,已经是去年的了一直没来的及发,今天抽出来一丢丢的空挡发一下。主要就是利用线矢量等距离生成点矢量,或者直接将线矢量等分生成点矢量,这个需求其实极限一下就是线转点了(将距离设置小一点)。顺便将点生成矩形面的代码也给出来,这里的......
  • 3分钟看懂设计模式02:观察者模式
    一、什么是观察者模式观察者模式又叫做发布-订阅模式或者源-监视器模式。结合它的各种别名大概就可以明白这种模式是做什么的。其实就是观察与被观察,一个对象(被观察者)的状态改变会被通知到观察者,并根据通知产生各自的不同的行为。以下为《设计模式的艺术》中给出的定义:观察者......
  • CreateHolesInImage说明文档-对于遥感影像的空洞创建多边形矢量数据
    提取遥感影像的空洞地理处理工具箱特点:通用地理处理工具,支持任何遥感影像,包括无人机,卫星遥感,普通图片和gdb,mdb数据库等。速度快,极致效率,效率高,支持对多个文件夹下的任意多数据进行批处理使用简单,全自动话,无人工干预功能:提取空洞提取空洞和非空洞默认临时文件夹,结果文件夹默认临时......
  • 在K8S中,请问harbor的secret创建能否直接创建资源清单?
    答案:当然可以,在Kubernetes(简称K8S)中,为了允许集群中的Pod能够从Harbor私有仓库拉取镜像,您可以直接通过编写资源清单(YAML文件)来创建一个Secret对象。这个Secret将包含访问Harbor所需的认证信息。以下是一个示例:apiVersion:v1kind:Secretmetadata:name:harbor-registry-secr......
  • 02. Create Project 创建项目导入素材
    使用版本我因为需要开发微信小游戏,所以使用的是2022.3.8f1c1版本创建项目首先创建一个3D项目,因为后续我们需要学习如何将一个普通的项目升级为URP通用渲染管线安装URP通用渲染管线在PackageManager中搜索Universal,然后安装UniversalRP。2022版本的Unity不需......
  • [设计模式]创建型模式-抽象工厂模式
    简介抽象工厂模式是一种创建型设计模式,它提供了一种创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。抽象工厂模式将一组具有共同主题的单个工厂封装起来,它提供接口用于创建相关或依赖对象的家族,而不需要指定具体的类。抽象工厂模式包含以下几个核心角色:抽象工厂(A......
  • 5 - 设备文件创建
    DeviceFileCreation原文链接我的博客创建字符设备设备文件在上一个小节中,我们知道了如何分配主副设备号,但是到此为止,只是创建主副设备号。并未在/dev目录下创建设备文件。设备文件设备文件可以实现用户空间应用与硬件的通讯。它们并不是普通文件,只是从编程视角来看是一......
  • RAID类型介绍、创建、彻底删除
    目录一、RAID(磁盘阵列)    1.1、概念    1.2、RAID0(条带化存储)    1.3、RAID1(镜像存储)    1.4、RAID5     1.5、RAID6       1.6、RAID1+0(先做镜像,再做条带)    1.7、RAID0+1(先做条带,再做镜像......
  • 3分钟看懂设计模式01:策略模式
    一、什么是策略模式定义一些列算法类,将每一个算法封装起来,并让它们可以互相替换。策略模式让算法独立于使用它的客户而变化,是一种对象行为型模式。以上是策略模式的一般定义,属于是课本内容。在没有真正理解策略模式之前并不需要对此定义下过多功夫,读一遍直接进入下一章节。二......
  • docker安装部署mysql8以及创建数据库
    mysql安装dockerpullmysql:8.0#创建mysql相关目录mkdir-p/server/data/docker/mysql/master/datadirdockerrun--namemysql-p53306:3306--restart=always--privileged=true\-v/server/data/docker/mysql/master/datadir:/var/lib/mysql\-v/s......