TypeScript中的单件设计模式

基本概念
(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-valuepublic 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-valuepublic 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 = 3private 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.rhkb.cn/news/214057.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu安装docker及docker常用命令

docker里有三个部分 daemon 镜像 和 容器 我们需要了解的概念 容器 镜像 数据卷 文章目录 docker命令docker镜像相关命令docker容器相关命令数据卷ubuntu安装docker docker命令 #启动,停止,重启docker systemctl start docker systemctl stop docker s…

pytorch:YOLOV1的pytorch实现

pytorch:YOLOV1的pytorch实现 注:本篇仅为学习记录、学习笔记,请谨慎参考,如果有错误请评论指出。 参考: 动手学习深度学习pytorch版——从零开始实现YOLOv1 目标检测模型YOLO-V1损失函数详解 3.1 YOLO系列理论合集(Y…

Notes数据直接在Excel中统计

大家好,才是真的好。 我希望你看过前面两篇内容《Domino REST API安装和运行》和《Domino REST API安装和运行》,因为今天我们正是使用REST API方式在Excel中查询和统计Notes数据。 不过首先你得知道一个OData协议,全名Open Data Protocol(…

配置OSS后如何将服务器已有文件上传至OSS,推荐使用ossutil使用

1.下载安装ossutil sudo -v ; curl https://gosspublic.alicdn.com/ossutil/install.sh | sudo bash2.交互式配置生成配置文件 ossutil config 根据提示分别设置配置文件路径、设置工具的语言、Endpoint、AccessKey ID、AccessKey Secret和STSToken参数,STSToken留…

Android 分享小结

关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。 目录 一、导读二、微信 分享 三、 QQ 、QQ空间(Qz…

python安装与工具PyCharm

摘要: 周末闲来无事学习一下python!不是你菜鸡,只不过是对手太强了!所以你要不断努力,去追求更高的未来!下面先了解python与环境的安装与工具的配置! python安装: 官网 进入官网下载…

iOS分段控件UISegmentedControl使用

在故事板中添加UISegmentedControl 具体添加步聚如下: 选择Xcode的View菜单下的Show Library (或者Shift+Common+L) 打开控件库如下 在控件库中输入seg搜索控件,在出现Segmented Control后,将其拖到View Controller Scene中 到这里,添加分段控件UI已完成, 接下来将控件与变量…

Qt/C++视频监控拉流显示/各种rtsp/rtmp/http视频流/摄像头采集/视频监控回放/录像存储

一、前言 本视频播放组件陆陆续续写了6年多,一直在持续更新迭代,视频监控行业客户端软件开发首要需求就是拉流显示,比如给定一个rtsp视频流地址,你需要在软件上显示实时画面,其次就是录像保存,再次就是一些…

HarmonyOS--ArkTS(1)--基本语法(1)

目录 基本语法概述 声明式UI描述 自定义组件 创建自定义组件 自定义组件的结构--struct ,Component,build()函数 生命周期 基本语法概述 装饰器: 用于装饰类、结构、方法以及变量,并赋予其特殊的含义。如上述示例中Entry、C…

AWS Remote Control ( Wi-Fi ) on i.MX RT1060 EVK - 2 “架构 AWS”

接续上一章节,我们把开发环境架设好之后,此章节叙述如何建立 AWS IoT 环境,请务必已经有 AWS Account,申请 AWS Account 之流程将不在此说明。 III-1. 登入AWS IoT, 在“管理”>“所有装置”>“实物”下点击“建…

Avalonia中如何将View事件映射到ViewModel层

前言 前面的文章里面我们有介绍在Wpf中如何在View层将事件映射到ViewModel层的文章,传送门,既然WPF和Avalonia是两套不同的前端框架,那么WPF里面实现模式肯定在这边就用不了,本篇我们将分享一下如何在Avalonia前端框架下面将事件…

【React Hooks】=> useId()

相比较使用全局变量 作为唯一 ID 和直接使用 useId 是有区别的。 官方解释如下: 如果是将 useId 作为 id 的情况下,是如下的形式 也就是说你使用了 useId 作为唯一 ID 那么在你删除数组某个元素之后不会导致某个 ID 被重复使用,如果使用的全…

分布式环境下的session 共享-基于spring-session组件和Redis实现

1、问题概述 不是所有的项目都是单机模式的,当一个项目服务的局域比较广,用户体量比较大,数据量较大的时候,我们都会将项目部署到多台服务器上,这些个服务器都是分布在不同的区域,这样实现了项目的负载和并…

工作实践中如何使用ThreadLocal?

主要作用 多线程问题主要是多个线程共享一个对象导致的,我们不让他共享就行了,每个线程保存一份自己的对象,自己玩自己的对象,就不会出现线程问题了。 ThreadLocal这个作用就是让线程自己独立保存一份自己的变量副本。每个线程都…

自动驾驶学习笔记(十六)——目标跟踪

#Apollo开发者# 学习课程的传送门如下,当您也准备学习自动驾驶时,可以和我一同前往: 《自动驾驶新人之旅》免费课程—> 传送门 《Apollo 社区开发者圆桌会》免费报名—>传送门 文章目录 前言 匹配关联 轨迹记录 状态预测 总结 前…

《Kafka权威指南》读书笔记

《Kafka权威指南》第一、三、四、六章,是重点。可以多看看。 一、 Kafka的组成 kafka是一个发布与订阅消息系统消息:kafka的数据单元称为"消息"。可以把消息看成是数据库中的一个"数据行"。 消息的key:为key生成一个一…

【工厂方法】设计模式项目实践

前言 以采集数据处理逻辑为例,数据采集分为不同种类如:MQTT、MODBUS、HTTP等,不同的采集数据有不同的解析处理逻辑。但总体解析处理步骤是固定的。可以使用工厂方法设计模式简化代码,让代码变得更加优雅。 代码实践 抽象类 总体…

蒙特霍尔问题(选择三扇门后的车与羊)及其贝叶斯定理数学解释

1. 蒙特霍尔问题 有一个美国电视游戏节目叫做“Let’s Make a Deal”,游戏中参赛者将面对3扇关闭的门,其中一扇门背后有一辆汽车,另外两扇门后是山羊,参赛者如果能猜中哪一扇门后是汽车,就可以得到它。 通常&#xf…

基于PicGo实现Typora图片自动上传GitHub

文章目录 一. 引言二. 原理三. 配置3.1 GitHub 设置3.2 下载配置 PicGo3.3 配置 Typora3.4 使用 一. 引言 Typora是一款非常好的笔记软件,但是有一个比较不好的地方:默认图片是存放在本地缓存中。这就会导致文件夹一旦被误删或电脑系统重装而忘记备份文件…

爬虫解析-jsonpath (六)

jsonpath只能解析本地文件 jsonpath的使用: obj json.load(open(.json文件,r,encodingutf-8))place_name jsonpath.jsonpath(obj, json语法) 目录 1.安装jsonpath 2.Xpath和jsonpath的语法对比 练习:使用jsonpath解析JSON文件 3.使用jsonpath抓取…