【JavaScript】设计模式(单例、策略、发布-订阅)

💻 【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) 优缺点

  1. 优点
    • 对象之间解耦
    • 异步编程中,可以更松耦合的代码编写
  2. 缺点
    • 创建订阅者本身要消耗一定的时间和内存
    • 虽然可以弱化对象之间的联系,多个发布者和订阅者嵌套一起的时候,程序难以跟踪维护

结束语

希望对您有一点点帮助,如有错误欢迎小伙伴指正。
👍点赞:您的赞赏是我前进的动力!
⭐收藏:您的支持我是创作的源泉!
✍评论:您的建议是我改进的良药!
一起加油!!!💪💪💪

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁星学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值