TypeScript中的单件设计模式

news2024/11/16 8:58:02
基本概念
(1) 了解设计模式

设计模式通俗的讲,就是一种更好的编写代码方案,打个比喻:从上海到武汉,你可以选择做飞机,做轮船,开车,骑摩托车多种方式,把出行看成是编码,那么选择飞机相对就是一个更好选择的优化方案。

(2) 常见设计模式概述

常见的设计模式有单件设计模式,简单工厂设计模式,工厂方法,抽象工厂设计模式,观察者设计模式,装饰设计模式,代理设计模式,MVC,MVP, MVVM 架构设计模式。本文讲解单件设计模式,原因有两个: 1. 设计模式并非 TypeScript 的重点,我们要把更多时间留给TS核心技能。 2. 单件设计模式虽短小精悍,但能更好的帮助掌握 TS 类,类的静态方法,类构造器,类对象的联合运用。

(3)单件设计模式的两种定义和定义中的存在的陷阱

简明定义1:一个类对外有且仅有一个实例【只提供一个实例】,这种编码方案就是单件设计模式。

完整定义1:如果某个类对外始终只提供一个对象【实例】,并且在该类的内部提供了一个外部访问该对象的方法或该对象属性,那么这种编写代码方案【就是设计模式】就是单件设计模式。

完整定义2:如果一个类的任何外部通过访问类提供的某个方法或某个属性始终只能获取该类一个对象【实例】,但如果该类提供了多个外部可以访问的方法或属性,那么外部就能访问到该类的多个不同的对象,但从实际开发来看,绝大多数情况的应用场景,我们对外都只提供一个唯一的可以访问的方法或属性,这样就保证了实例为单个,类的这种编写代码的方案【就是设计模式】就是单件设计模式。

(4) 何时需要使用单件设计模式?

实际开发中,外部访问某个类的对象【实例】时,确保只能访问该类的唯一对象时才能保证逻辑的正确性时,这时就应该使用单件设计模式了。

(5)前端领域单件设计模式的真实应用场景

应用场景1:比如 Vuex,React-Redux 中的全局状态管理容器 store 对象在整个项目被设计成唯一的对象【实例】,把 store 对象所在 的类设计成单件设计模式将是最好的设计方案 【当然也可以有其他替代写法】

应用场景2:一般前端项目需要进行客户端本地数据存储时,都会考虑使用 localStorage,localStorage只要在相同的协议、相同的主机名、相同的端口下,就能读取/修改到同一份 localStorage 数据。那封装 localStorage设计成一个单件设计模式类就再合适不过了。

应用场景3:我们知道项目日志记录是一个项目中必不可少的环节,当我们为一个项目编写一个日志文件类,用来保存日志和阅读日志信息时,这个日志文件类可以有多种设计方案,但把类写成单件模式是最好的方案,因为每次存储日志信息到日志文件上时都创建一个日志对象,这既没有必要,也很浪费内存空间。

针对应用场景2,编码实现:
(1)不使用单件设计模式:
// 下面是平时开发过程中非常常见的保存数据到本地存储的方式

localStorage.setItem("count", "30")

let loginInfoObj = { username: "lisi", age: 23 }

localStorage.setItem("loginUser", JSON.stringify(loginInfoObj))

let value = localStorage.getItem("loginUser")
value != null ? JSON.parse(value) : null;
//console.log(value.username)

// 问题1:代码零散
// 问题2:可读性差,不能一下子就能顾名思义
// 问题3:对后期的维护产生影响
// 问题4: JSON.stringify,JSON.parse可以直接放到类中,如果这样写的多,就影响了开发效率

(2)使用单件设计模式:
// 构建单件设计模式
//   第一步:把构造器设置为私有的,不允许外部来创建类的实例【对象】
//   第二步: 至少应该提供一个外部访问的方法或属性,外部可以通过这个方法或属性来得到一个对象
//           所以应该把这个方法设置为静态方法
//   第三步:外部调用第二步提供的静态方法来获取一个对象
export default class MyLocalStorage {
  count3!: number;
  // 静态属性和对象属性[实例属性】是类中两大成员
  static localstorage: MyLocalStorage//静态引用属性
  static count: number = 3;//静态的基本类型属性
  constructor() {
    console.log("这是TS的单件设计模式的静态方法的构造器");
  }
  // 提供一个外部访问的方法,
  // 通过这个方法用来提供外部得到一个对象的方法
  //   1. 带static关键字的方法就是一个静态方法
  //   2. 静态方法和对象无关,外部的对象变量不能调用静态方法和静态属性,
  //   3. 外部可以通过类名来调用
  //   静态方法不可以访问实例属性或实例方法【对象属性或对象方法】
  public static getInstance() { // 每次调用保证只会返回相同的实例对象
    if (!this.localstorage) {//如果静态对象属性指向创建对象
      console.log("我是一个undefined的静态属性,用来指向一个对象空间的静态属性")
      this.localstorage = new MyLocalStorage()
    }
    return this.localstorage
  }

  public other() {

  }
  public test() {

  }
  // 保存key-value
  public setItem(key: string, value: any) {

    localStorage.setItem(key, JSON.stringify(value))
  }


  public getItem(key: string) {

    let value = localStorage.getItem(key)
    return value != null ? JSON.parse(value) : null;

  }
}
融合单件设计模式深入掌握静态属性,静态方法

1. 外部如何调用 TS 类的静态成员?

答:类名直接调用静态成员,格式:类名.静态属性 类名.静态方法。

2. TS类的一个静态方法如何调用其他的静态成员?

答:使用 this 来获取静态成员。

3. 静态方法是否可以访问类中原型对象上的方法或对象属性【对象基本类型数据+对象引用属性】,反过来呢? 答:都不能。

4. 对象变量是否可以访问静态成员?

答:不能。

5. 一个静态方法改变了某个静态属性,其他静态方法或类外部任何地方访问这个属性都会发生改变。

6. 静态成员保存在内存哪里?何时分配的内存空间呢?

答:任何一个 TS 类中的静态成员存储在内存的静态区,运行一个 TS 类,TS首先会为静态成员开辟内存空间,静态成员的内存空间分配的时间要早于对象空间的分配,也就是任何一个对象创建之前 TS 就已经为静态成员分配好了空间。但一个静态方法或静态属性只会分配一个空间,只要当前服务器不重启或控制台程序还没有结束之前【如果是开发期间临时测试,一般用控制台】,那么静态方法或者是静态属性就一直存在内存空间,无论调用多少次这个静态方法或静态属性,都是调用的同一块空间。

总结静态方法,两点:

总结1: 无论你是否创建对象,创建多少个对象,是否调用该静态方法或静态属性,TS都会为这个静态方法或静态属性分配内存空间,注意:静态成员和对象无关。

总结2:一旦为静态方法或静态属性分配好空间,就一直保存到内存中,直到服务器重启或者控制台程序执行结束才被释放。
请添加图片描述

7. 静态方法或属性和原型对象空间上的方法或属性有何区别?

答:原型对象空间上的方法和属性用来提供给该类的所有对象变量共用的方法或属性,没有对象和对象变量,原型上的属性和方法就没有了用武之地,而静态方法或静态属性属于类,可以通过类来直接访问。任何一个对象创建之前 TS 就已经为静态成员分配好了空间。但一个静态方法或静态属性只会分配一个空间,而每一个对象都有自己独立的空间。

8. 静态方法是否可以接受一个对象变量来作为方法的参数?

答:可以,静态方法内部不能通过this来访问对象属性和方法,但可以通过调用静态方法时把对象变量传递给静态方法来使用。比如:我们把 js 的 Object 构造函数想象成一个 TS 类【实际 TS 类编译后的 JS 文件中就变成了一个构造函数】。Object 类就拥有大量的静态方法,例如:apply,call,bind,keys等,现在我们来关注静态方法是否可以接受对象变量作为方法的参数,我们以Object.keys方法为例 【Object类的keys方法用来获取给定对象的自身可枚举属性组成的数组】。

// 我们现在把 Object 构造函数看成一个 Object 类,创建 Object 类的对象。
let obj = new Object({ username: "wangwu", age: 23 })//1
let obj2 = { username: "wangwu", age: 23 }// 2是1的简写
// 把 obj 对象变量传递给 keys静态方法,obj对象变量作为 keys 静态方法的参数
Object.keys(obj2)
9. 何时应该把一个方法定义成静态方法或属性定义为静态属性呢?【应用】

应用1:单件设计模式就是静态方法和静态属性很好的应用场景之一。当外部不能创建对象,就只能借助类内部的静态方法来获取类的对象;这时肯定不能把这个方法定义成原型对象属性上的方法,只能定义为类的静态方法,因为如果定义成原型对象属性的方法,就会导致外部无法被访问,因为外部根本不能创建对象,也就无法访问原型对象属性上的方法。而静态方法要访问的属性就只能是静态属性了,这也是静态属性的应用时机。

应用2: 当类中某个方法没有任何必要使用任何对象属性时,而且使用了对象属性反而让这个方法的逻辑不正确,那既如此,就应该禁止这个方法访问任何对象属性和其他的对象方法,这时就应该把这个方法定义为静态方法。例如:一个顾客类的购买方法【 buy 方法】中肯定要允许访问顾客姓名或其他顾客微信这些对象属性,这样的方法我们就需要定义在原型对象属性上,但如果顾客类中的 阅读顾客积分公告方法【 readNotice 方法] 是针对全体顾客的公告方法,就应该定义为静态方法,方法内部就应该禁止出现任何具体的对象属性。如果在这样的方法中使用了顾客的某个属性,比如用了顾客姓名,那么这个方法逻辑就不正确【这个方法就会说:你让我向全体顾客展示公告,你我要知道每个顾客姓名做什么?】。所以我们应该让这样的方法禁止访问对象属性和其他的对象方法,那就应该设置为静态方法。

应用3:当一个类中某个方法只有一个或者 1-2个 对象属性,而且更重要的是,你创建这个类的对象毫无意义,我们只需要使用这个类的一个或者多方法就可以了,那么这个方法就应该定义为静态方法。常见的工具类中的方法通常都应该定义为静态方法。比如 StringUtil, FileUtil 等,我们以 FileUtil 为例进行讲解

思考题:定义一个文件工具类【 FileUtil 】,编写一个读取文件方法【readFile方法】方便外部调用,那这样的方法应该定义为静态方法吗?

答:定义在原型属性上和定义为静态方法似乎都可以,只要 readFile 方法获取到外部提供文件名就可以展开文件读写。请看下面两段代码,我们仔细比较后再来决定用哪一种方案?

class FileUtil{
	// 从指定文件上把数据读出来打印在控制台或页面上的静态方法
   public static readFile(readonly fileName:string){  
      fs.readFile(fileName, (err: any, data: any) => {
          console.log("fs.readFile:", data.toString());
      }
   }
  // 把键盘输入的数据或页面上获取的数据写入到指定文件上的静态方法
   public static writeFile(fileName:string){
       fs.writeFile(fileName, '刘老根4', function (error) {
          if (error) {
            console.log('写文件失败')
          } else {
            console.log('写文件成功')
          }
	})
 
 }
 // 实际应用中,读和写一般都不在一个时间段,可能读功能完成后,过了几分钟,用户才在客户端执行写的方法,
 // 又过了一会,用户又在客户端执行了读的方法。 但我们知道静态方法实际上是一直保存到内存空间,这样反复操作其实节省了大量反复创建 和释放 FileUtil 对象的时间和对应的对象内存空间。
 FileUtil.readFile('./log.txt')
 FileUtil.writeFile('./log5.txt')
class FileUtil{
   constructor(public fileName:string){}
  
   // 从指定文件上把数据读出来打印在控制台或页面上的静态方法
   public  readFile(){  
      fs.readFile(fileName, (err: any, data: any) => {
          console.log("fs.readFile:", data.toString());
      }
   }
   
    // 把键盘输入的数据或页面上获取的数据写入到指定文件上的静态方法
    public writeFile(fileName:string){
       fs.writeFile(fileName, '刘老根4', function (error) {
          if (error) {
            console.log('写文件失败')
          } else {
            console.log('写文件成功')
          }
	})
 }
 // 实际应用中,读和写一般都不在一个时间段,可能读功能完成后,过了几分钟,用户才在客户端执行写的方法,
 // 又过了一会,用户又在客户端执行了读的方法。所以每次都要创建 FileUtil 对象,这样反复创建 和释放  FileUtil 对象,就浪费了大量反复创建 和释放 FileUtil 对象的时间和对应的对象内存空间
 new FileUtil('./log.txt').readFile()
 new FileUtil('./log5.txt').writeFile()
10. 对于第 9 项思考题中的关于使用静态属性或静态方法的解决方案绝对不能用在学生,顾客其他应用场景,那样会导致三个比较严重的问题,以学生对象为例:
  1. 浪费了很多不必要的内存空间

运行一开始就为大量的静态属性和大量的静态方法分配内存空间【但很可能某个静态方法一直没有使用,白白的一直占用着内存空间】

  1. 无法展示一个学生一个对象的直观效果,完全失去了对象来描述实体的优势!

  2. 最严重的问题是:属性值一变则都变

所有操作都在用一个静态方法空间来完成某种功能,一旦某个操作改变了静态方法中的某个值,比如改变了学生姓名,则其他操作访问到这个静态变量看到的结果全变了。

用单件模式解决以上问题

(1)懒汉式单件设计模式实现步骤

构建单件设计模式[懒汉式[等到需要使用对象时才创建对象,按需创建]单件设计模式 ]

第一步:把构造器设置为私有的,不允许外部来创建类的实例【对象】

第二步: 至少应该提供一个外部访问的方法或属性,外部可以通过这个方法或属性来得到一个对象

所以应该把这个方法设置为静态方法

第三步:外部调用第二步提供的静态方法来获取一个对象

class MyLocalStorage {
  // 静态属性和对象属性[实例属性】是类中两大成员
  static localstorage: MyLocalStorage//引用静态属性
  private constructor() {
    console.log("这是TS的单件设计模式的静态方法的构造器");
  }
  // 提供一个外部访问的方法,
  // 通过这个方法用来提供外部得到一个对象的方法
  //   1. 带static关键字的方法就是一个静态方法
  //   2. 静态方法和对象无关,外部的对象变量不能调用静态方法和静态属性,
  //   3. 外部可以通过类名来调用
  //   静态方法不可以访问实例属性或实例方法【对象属性或对象方法】
  public static getInstance() {
    // let localstorage = new MyLocalStorage();
    // return localstorage
    // return new MyLocalStorage();
    // 使用局部变量来解决 失败了
    // let localstorage
    // if (!localstorage) {
    //   localstorage = new MyLocalStorage();
    // }
    // return localstorage
    //this.setItem
    // if(!false){ undefined null 0 false

    //}
    if (!this.localstorage) {//如果静态对象属性指向创建对象
      console.log("我是一个undefined的静态属性,用来指向一个对象空间的静态属性")
      this.localstorage = new MyLocalStorage()

    }

    return this.localstorage
  }

  public test() {

  }
  // public static getInstanceNew(){
  //   this.
  // }
  // 保存key-value
  public setItem(key: string, value: any) {

    localStorage.setItem(key, JSON.stringify(value))
  }


  public getItem(key: string) {

    let value = localStorage.getItem(key)
    return value != null ? JSON.parse(value) : null;

  }
}

(2)饿汉式单件设计模式实现步骤

构建单件设计模式[饿汉式单件设计模式 立即创建对象]

第一步:把构造器设置为私有的,不允许外部来创建类的实例【对象】

第二步: 建立一个静态引用属性,同时把这个静态引用属性直接指向一个对象【 new MyLocalStorage()】

第三步:外部调用第二步提供的静态方法来获取一个对象

class MyLocalStorage {
  // 对象属性【对象的基本类型属性和对象的引用属性】
  // 静态属性【静态的基本类型属性和静态的引用属性】
  static localstorage: MyLocalStorage = new MyLocalStorage();
  static count: number = 3
  private constructor() {
    console.log("这是TS的单件设计模式的静态方法的构造器");
  }
  public setItem(key: string, value: any) {

    localStorage.setItem(key, JSON.stringify(value))
  }
  public getItem(key: string) {

    let value = localStorage.getItem(key)
    return value != null ? JSON.parse(value) : null;

  }
}

脚踏实地行,海阔天空飞

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1299404.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Kubernetes】四层代理Service

Service四层代理 一、Service概念原理1.1、为什么要有Service1.2、Service概述1.3、工作原理1.4、三类IP地址【1】Node Network(节点网络)【2】Pod network(pod 网络)【3】Cluster Network(服务网络) 二、S…

四川技能大赛——2023年四川网信人才技能大赛(网络安全管理员赛项)决赛

四川技能大赛——2023年四川网信人才技能大赛(网络安全管理员赛项)决赛 文章目录 四川技能大赛——2023年四川网信人才技能大赛(网络安全管理员赛项)决赛C1-比64少的bas - DONEC2-affine - DONEC3-简单的RSA - DONEM1-不要动我的f…

【C++数据结构 | 字符串速通】10分钟秒杀字符串相关操作 | 字符串的增删改查 | 字符串与数组相互转换

字符串 by.Qin3Yu 文中所有代码默认已使用std命名空间且已导入部分头文件&#xff1a; #include <iostream> #include <string> using namespace std;概念速览 字符串是一种非常好理解的数据类型&#xff0c;它用于存储和操作文本数据。字符串可以包含任意字符…

认识存储管理

存储器是计算机系统中最重要的资源之一。因为任何程序和数据以及各种控制用的数据结构都必须占有一定的存储空间&#xff0c;因此&#xff0c;存储管理直接影响系统性能。 存储器由内存和外存组成。内存是由系统实际提供的存储单元&#xff08;常指字节&#xff09;组成的一个连…

delphi android打开外部文件,报错android.os.FileUriExposedException解决方法

Android 7.0强制启用了被称作 StrictMode的策略&#xff0c;带来的影响就是你的App对外无法暴露file://类型的URI了。 如果你使用Intent携带这样的URI去打开外部App(比如&#xff1a;打开系统相机拍照)&#xff0c;那么会抛出FileUriExposedException异常。 Delphi 为Android…

spring集成mybatis简单教程

首先说下实现了什么效果&#xff0c;就是不用每次查询前手动创建 sessionFactory和添加datasource文件了。 整个工程结构是这样的 这次我也把代码放在了gitee上&#xff0c;方便大家更全貌的看到所有的实现细节。代码链接如下&#xff1a; Java: 一些Java代码 (gitee.com) …

交换机基本原理和配置

目录 一、数据链路层功能 二、交换机的工作原理 三、交换机的四大功能 一、数据链路层功能 位于网络层与物理层之间 数据链路的建立、维护与拆除帧包装、帧传输、帧同步帧的差错恢复流量控制 二、交换机的工作原理 交换机通过数据帧的源 MAC 地址&#xff0c;学习到交换机端…

生成模型 | 数字人类的三维重建(3D reconstruction)调研及总结【20231210更新版】

本文主要集中于图片到三维重建的算法模型&#xff0c;其中包含人体重建&#xff0c;人脸重建等 1.三维人体重建 1.1.2015_SMPL: A Skinned Multi-Person Linear Model 论文地址&#xff1a;SMPL2015.pdf (mpg.de) 代码地址&#xff1a;CalciferZh/SMPL: NumPy, TensorFlow an…

我的隐私计算学习——隐私集合求交(1)

笔记内容来自多本书籍、学术资料、白皮书及ChatGPT等工具&#xff0c;经由自己阅读后整理而成。 &#xff08;一&#xff09;PSI的介绍 隐私计算关键技术&#xff1a;隐私集合求交&#xff08;PSI&#xff09;原理介绍 隐私计算关键技术&#xff1a;隐私集合求交&#xff08…

利用Node.js和cpolar实现远程访问,无需公网IP和路由器设置的完美解决方案

文章目录 前言1.安装Node.js环境2.创建node.js服务3. 访问node.js 服务4.内网穿透4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5.固定公网地址 前言 Node.js 是能够在服务器端运行 JavaScript 的开放源代码、跨平台运行环境。Node.js 由 OpenJS Foundation&#xff0…

Python开发运维:Python垃圾回收机制

目录 一、理论 1.Python垃圾回收机制 一、理论 1.Python垃圾回收机制 &#xff08;1&#xff09;引⽤计数器 1&#xff09;环状双向链表 refchain 在python程序中创建的任何对象都会放在refchain链表中。 name "david" age 20 hobby ["篮球",游泳…

【链表Linked List】力扣-114 二叉树展开为链表

目录 题目描述 解题过程 官方题解 题目描述 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应…

渗透测试——十、渗透列举及命令详解

渗透测试 一、协议配置与分析1、HTTPS 的定义2、HTTPS的验证 二、Kali Linux 常用工具三、Windows 命令详解 一、协议配置与分析 1、HTTPS 的定义 HTTPS (Hyper Text Transfer Protocol over Secure Socket Layer&#xff0c;超文本传输安全协议)是以安全为目标的 HTTP 通道。…

IDEA Maven 配置国内源

基本步骤 分别设置下图的两个&#xff0c;一个是对当前项目的设置&#xff0c;一个是对以后创建的项目设置&#xff0c;这样以后就不用重新配置了。 将下面的两个勾选上 注意&#xff0c;两个地方&#xff0c;Settings 和 Settings for New Projects 的勾都要勾上。 前往 User…

【C语言基础】嵌入式面试经典题(C语言篇)----有新的内容会及时补充、更新!

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

WorkPlus即时通讯app,支持私有化部署的企业IM

当企业面临复杂的协同、业务和生态场景时&#xff0c;多个繁琐的应用和系统常常让员工头疼不已。然而&#xff0c;WorkPlus作为企业数字化转型的超级APP&#xff0c;以其一站式全能解决方案&#xff0c;为企业带来了颠覆性的便利与高效。本文将深入探讨WorkPlus的特点和优势&am…

JavaScript系列-数据类型

ES6变量类型 JavaScript编程语言中&#xff0c;变量类型分为基本变量类型和引用类型&#xff0c;两种变量类型的区别在于 基本类型变量值存放于栈中&#xff0c;引用类型变量值存放于堆中基本类型赋值给其他变量&#xff0c;是将其值复制过去引用类型赋值给其他变量&#xff…

MyString:string类的模拟实现 1

MyString&#xff1a;string类的模拟实现 前言&#xff1a; 为了区分标准库中的string&#xff0c;避免编译冲突&#xff0c;使用命名空间 MyString。 namespace MyString {class string{private:char* _str;size_t _size;size_t _capacity;const static size_t npos -1;// C标…

解锁MySQL的威力:针对常见问题的快速解决指南

数据库和表的创建 创建数据库&#xff1a; CREATE DATABASE IF NOT EXISTS MyDatabase; USE MyDatabase;案例&#xff1a; 想象您要开始一个博客项目。首先&#xff0c;您需要一个地方来存储所有的文章和用户信息。上述命令帮助您创建了这样一个存储空间&#xff0c;名为MyDa…

人力资源服务展示网站作用有哪些

就业劳务问题往往是不少人群关注的问题&#xff0c;每个城市都聚集着大量求业者&#xff0c;而人力资源管理公司每年也会新增不少&#xff0c;对求业者来说&#xff0c;通过人力资源公司可以快速便捷的找到所需工作&#xff0c;而对公司来说&#xff0c;市场大量用户可以带来收…