💻 【JavaScript】设计模式(单例、策略、发布-订阅) 🏠专栏:JavaScript
👀个人主页:繁星学编程🍁
🧑个人简介:一个不断提高自我的平凡人🚀
🔊分享方向:目前主攻前端,其他知识也会阶段性分享🍀
👊格言:☀️没有走不通的路,只有不敢走的人!☀️
👉让我们一起进步,一起成为更好的自己!!!🎁
文章目录
【JavaScript】设计模式
一. 单例模式
(1) 概念
单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式核心:实例化一个类的时候
先判断,之前有没有实例化过
- 如果有,就用之前的
- 如果没有,就实例化一个新的
单例模式优点:创建对象和管理单例的职责被分布在两个不同的方法中
(2) 场景
单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的 window 对象等。
在 JavaScript 开发中,单例模式的用途同样非常广泛。试想一下,当我们单击登录按钮的时候,页面中会出现一个登录浮窗,而这个登录浮窗是唯一的,无论单击多少次登录按钮,这个浮窗都只会被创建一次,那么这个登录浮窗就适合用单例模式来创建。
(3) 案例:书写一个弹窗
/*分析:
找到一个变量,初始化的时候是null
第一次实例化后,给这个变量赋值
第二次想要实例化的时候,先去看看这个变量有没有值,如果有,就用之前的
// 一个弹窗的构造函数
class Dialog {
constructor(title) {
this.title = title
this.div = document.createElement('div')
this.div.style.backgroundColor = 'pink'
document.body.appendChild(this.div)
}
changeTile(title) {
this.title = title
this.div.innerHTML = this.title
}
}
// 单例模式代码
// 为了避免instance这个变量污染 改成闭包
const singleton = (function () {
// 提前设置一个变量,用来记录该构造函数有没有实例化过
let instance = null
return function (title) {
// 判断构造函数有没有实例化过
// 没有实例化过
if (!instance) {
instance = new Dialog(title)
}
// 如果有,就返回这个实例化对象
return instance
}
})()
// 创建弹窗对象
const res = singleton('hello')
res.changeTile('小花')
// 再次创建弹窗对象
const res1 = singleton('你好')
res1.changeTile('你好')
console.log(res1); // Dialog {title: '你好', div: div}
二. 策略模式
(1) 概念
策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
策略模式的目的是:将算法的使用与算法的实现分离开来。
一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体 的算法,并负责具体的计算过程。 第二个部分是环境类Context,Context 接受客户的请求,随后 把请求委托给某一个策略类。要做到这点,说明 Context中要维持对某个策略对象的引用。
(2) 场景
从定义上看,策略模式就是用来封装算法的。但如果把策略模式仅仅用来封装算法,未免有一点大材小用。在实际开发中,我们通常会把算法的含义扩散开来,使策略模式也可以用来封装 一系列的“业务规则”。只要这些业务规则指向的目标一致,并且可以被替换使用,我们就可以 用策略模式来封装它们。
(3) 案例
/* 例子 已知一个商品总价 500
根据折扣计算实际价格
例如:80% 70% 1000-300 800-50
*/
// 通过闭包的形式计算折扣
const calcDiscount = (function () {
let priceList = {
"80%": function (total) { return (total * 0.8).toFixed(2) },
"70%": function (total) { return (total * 0.7).toFixed(2) },
"50%": function (total) { return (total * 0.5).toFixed(2) }
}
function inner(total, type) {
if (!priceList[type]) {
return "折扣错误";
}
// 反之折扣正确,返回计算后的价格
return priceList[type](total);
}
// 接下来根据需求定义一系列的算法
// 添加折扣
inner.add = function (type, fn) {
priceList[type] = fn;
}
// 删除折扣
inner.remove = function (type) {
delete priceList[type];
}
// 修改折扣
inner.change = function (type, fn) {
priceList[type] = fn;
}
// 查看折扣
inner.look = function () {
return priceList;
}
return inner;
})()
// 测试算法
// 1.计算价格
// const res = calcDiscount(1400, "80%");
// console.log(res); // 1120.00
// 2.添加折扣
// calcDiscount.add("40%", function (total) { return (total * 0.4).toFixed(2) });
// const res = calcDiscount(1500, "40%")
// console.log(res); // 600
// 3.删除折扣
// calcDiscount.remove("80%");
// const res = calcDiscount(1200, "80%");
// console.log(res); // 折扣错误
// 4.修改折扣
calcDiscount.change("70%", function (total) { return (total * 0.4).toFixed(2) });
const res = calcDiscount(1200, "70%");
console.log(res); // 480.00
// 5.查看折扣
// console.log(calcDiscount.look()); // {80%: ƒ, 70%: ƒ, 50%: ƒ}
(4) 优缺点
优点:
- 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
- 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的策略中,使得它们易于切换,易于理解,易于扩展。
- 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
- 在策略模式中利用组合和委托来让环境类拥有执行算法的能力,这也是继承的一种更轻便的替代方案。
缺点:
- 增加许多策略类或者策略对象,但实际上这比把它们负责的 逻辑堆砌在环境类中要好。
- 要使用策略模式,必须了解所有的策略,必须了解各个策略之间的不同点, 这样才能选择一个合适的策略。
三. 发布-订阅模式
(1) 什么是发布-订阅模式
1. 定义
发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。
订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
2. 例子
例子1:比如我们很喜欢看某个公众号号的文章,但是我们不知道什么时候发布新文章,要不定时的去翻阅;这时候,我们可以关注该公众号,当有文章推送时,会有消息及时通知我们文章更新了。
上面一个看似简单的操作,其实是一个典型的发布订阅模式,公众号属于发布者,用户属于订阅者;用户将订阅公众号的事件注册到调度中心,公众号作为发布者,当有新文章发布时,公众号发布该事件到调度中心,调度中心会及时发消息告知用户。
例子2:一个卖书的例子,当你去买书的时候,如果当时有你要买的书,可以直接买到当时当书店没有这本书的时候,你就需要先在书店预订,让老板等书到了通知你,而如果你等待的时候突然又不想买了你可以告诉老板你不要了。
在上述案例中,老板就是发布者,买者就是订阅者,不想要了就是取消订阅。
(2) 如何实现发布-订阅模式?
1. 实现思路
- 创建一个对象
- 在该对象上创建一个缓存列表(调度中心)
- add方法用来把函数 fn 都加到缓存列表中(订阅者注册事件到调度中心)
- emit 方法取到 arguments 里第一个当做 event,根据 event 值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码)
- remove 方法可以根据 event 值取消订阅(取消订阅)
2. 具体简单实现例子2:到书店买书
class Subscribe {
constructor() {
// 登记本:存放书籍和操作方法
this.message = {};
}
// 添加订阅消息
add(type, fn) {
// type:订阅的书籍
// fn:书籍到货后的方法
// 添加前,判断登记本上是否已经存在这本书籍
if (!this.message[type]) {
// 如果不存在,设置一个空数组
this.message[type] = [];
}
// 如果存在,不重复登记
if (this.message[type].indexOf(fn) !== -1) { return }
// 正常登记,添加对应的书籍数据
this.message[type].push(fn);
console.log(this.message);
}
// 所需的书籍到货,发布通知
emit(type) {
// 如果还没有这本书籍
if (!this.message[type]) { return }
// 如果书籍到货,通知所有想要这本书籍的人
this.message[type].forEach(item => item());
}
// 取消订阅
remove(type, fn) {
// 如果没有对应的订阅消息
if (!this.message[type]) { return }
// 取消该书籍所有的订阅消息
if (!fn) {
delete this.message[type];
return;
}
// 取消对应的订阅消息
this.message[type] = this.message[type].filter(item => item !== fn);
}
}
// 设置一个卖书的老板
const zs = new Subscribe();
// 进行操作
function fn1() {
console.log("小王需要一本");
}
function fn2() {
console.log("小张需要一本");
}
// 订阅
zs.add("西游记", fn1); // {西游记: Array(1)}
zs.add("西游记", fn2); // {西游记: Array(2)}
zs.add("三国演义", fn2); // {西游记: Array(2), 三国演义: Array(1)}
// 发布
zs.emit("西游记"); // 小王需要一本 小张需要一本
zs.emit("红楼梦");
// 取消订阅
zs.remove("西游记", fn1); // {西游记: Array(1), 三国演义: Array(1)}
zs.remove("红楼梦");
(3) 优缺点
- 优点
- 对象之间解耦
- 异步编程中,可以更松耦合的代码编写
- 缺点
- 创建订阅者本身要消耗一定的时间和内存
- 虽然可以弱化对象之间的联系,多个发布者和订阅者嵌套一起的时候,程序难以跟踪维护
结束语:
希望对您有一点点帮助,如有错误欢迎小伙伴指正。
👍点赞:您的赞赏是我前进的动力!
⭐收藏:您的支持我是创作的源泉!
✍评论:您的建议是我改进的良药!
一起加油!!!💪💪💪