JavaScript设计模式(十六):通信卫星-观察者模式

简介: 通信卫星-观察者模式

观察者模式(Observer)

又被称作发布-订阅者模式消息机制,定义了一种依赖关系,解决了主体对象与观察者之间功能的耦合,以及模块间通信问题

实现评论模块

  • 需求:
    1. 当用户发布评论时,会在评论展示模块末尾处追加新的评论;
    2. 与此同时用户的消息模块的消息数量也会递增;
    3. 如果用户删除留言区的信息时,用户的消息模块消息数量也会递减;
  • 存在的问题:
    • 这些模块的代码是三位不同的工程师写的,都写在自己的独立闭包模块里,现在我要完成我的需求,又不想将他们的模块合并到一起
<style>
    ul {
    
    
        list-style: none;
        border: 1px solid #333;
        padding: 5px;
        min-height: 100px;
    }

    ul li {
    
    
        word-break: break-all;
        padding: 5px;
        text-align: justify;
    }

    ul li::before,
    ul li::after {
    
    
        content: "";
        display: block;
        clear: both;
    }

    ul li button {
    
    
        float: right;
    }

    ul li:nth-child(2n) {
    
    
        background-color: #ccc;
    }

    ul li:nth-child(2n + 1) {
    
    
        background-color: #aaa;
    }
</style>

<body>
    <input id="user_input" />
    <button id="user_submit">Submit</button>
    <span id="msg_num"></span>
    <ul id="msg_box"></ul>
    <script src="./index.js"></script>
</body>

实现三位工程师的功能

image.png

// 原三位工程师实现的功能逻辑
function $(id) {
   
   
    return document.getElementById(id);
}

/**
 * 工程师 A
 *      实现了消息列表追加以及删除消息功能
 */
; (function () {
   
   
    /**
     * 追加一则消息,并可对消息进行删除
     * @param {string} msg 消息内容
     */
    function addMsgItem({
   
    msg }) {
   
   
        // 获取评论容器,创建消息条目,创建删除按钮
        const ul = $('msg_box'), li = document.createElement('li'), btn = document.createElement('button');
        // 配置删除按钮文字
        btn.innerText = 'Delete';
        // 删除点击事件 测试执行 ---> 点击进行测试
        btn.onclick = function () {
   
   
            ul.removeChild(li);
        };
        // 添加消息内容
        li.innerText = msg;
        // 添加删除按钮
        li.appendChild(btn);
        // 追加一则消息
        ul.appendChild(li);
    }
    // 测试执行
    addMsgItem({
   
   msg: 'Hello!'});
    addMsgItem({
   
   msg: 'Hi!'});
    addMsgItem({
   
   msg: '你好呀!'});
    addMsgItem({
   
   msg: '我不好呀!'});
    addMsgItem({
   
   msg: '你爱好不好呀!'});
}());

/**
 * 工程师 B
 *      实现了消息数目累加减功能
 */
; (function () {
   
   
    /**
     * 修改消息数量
     * @param {number} count 累加减数量 -1和1
     */
    function changeMsgCount({
   
    count }) {
   
   
        {
   
   mathJaxContainer[0]}('msg_num').innerText) + count;
    }
    // 测试执行
    changeMsgCount({
   
   count: 1});  // 1
    changeMsgCount({
   
   count: 1});  // 2
    changeMsgCount({
   
   count: -1}); // 1
    changeMsgCount({
   
   count: 1});  // 2
}());

/**
 * 工程师 C
 *      实现了消息提交的功能
 */
; (function () {
   
   
    /**
     * 用户点击提交按钮
     *      测试执行 ---> 点击进行测试
     */
    $('user_submit').onclick = function () {
   
   
        // 获取用户输入框中输入的信息
        const msg = $('user_input');
        // 如果输入框内容为空则退出函数
        if (msg.value === '') return;
        console.log(msg.value);
        // 点击完成后将输入框置为空
        msg.value = '';
    };
    // 测试执行 ---> 点击进行测试
}());

使用观察者模式整合这三个功能

image.png

function $(id) {
   
   
    return document.getElementById(id);
}

/**
 * 定义观察者
 */
const Observer = (function () {
   
   
    // 防止消息队列暴漏而被篡改故将消息容器作为静态私有变量保存
    let __messages = {
   
   };
    return {
   
   
        __messages,
        /**
         * 注册消息接口 - 暂存
         * @param {string} type 消息类型
         * @param {Function} fn 消息回调
         */
        regist(type, fn) {
   
   
            // 如果此消息不存在则应该创建一个该消息类型
            if (typeof __messages[type] === 'undefined') {
   
   
                // 将动作推入到该消息对应的动作执行队列中
                __messages[type] = [fn];
            }
            // 如果此消息存在
            else {
   
   
                // 将动作方法推入该消息对应的动作执行序列中
                __messages[type].push(fn);
            }
            return this;
        },
        /**
         * 发布消息接口 - 调用
         * @param {string} type 消息类型
         * @param {object} args 消息携带数据
         */
        fire(type, args) {
   
   
            // 如果该消息没有被注册,则返回
            if (!__messages[type]) return;
            // 遍历消息动作
            for (let i = 0; i < __messages[type].length; i++) {
   
   
                // 依次执行注册的消息对应的动作序列
                __messages[type][i].call(this, args);
            }
        },
        /**
         * 移除消息接口 - 移除
         * @param {string} type 消息类型
         * @param {Function} fn 消息回调
         */
        remove(type, fn) {
   
   
            // 如果消息动作队列存在
            if (__messages[type] instanceof Array) {
   
   
                // 从最后一个消息动作遍历
                for (let i = __messages[type].length - 1; i >= 0; i--) {
   
   
                    // 如果存在该动作则在消息动作序列中移除相应动作
                    __messages[type][i] === fn && __messages[type].splice(i, 1);
                }
            }
        }
    };
}());

/**
 * 工程师 A
 *      实现了消息列表追加以及删除消息功能
 */
; (function () {
   
   
    /**
     * 追加一则消息,并可对消息进行删除
     * @param {string} msg 消息内容
     */
    function addMsgItem({
   
    msg }) {
   
   
        // 获取评论容器,创建消息条目,创建删除按钮
        const ul = $('msg_box'), 
            li = document.createElement('li'), 
            btn = document.createElement('button');
        // 配置删除按钮文字
        btn.innerText = 'Delete';
        // 删除点击事件
        btn.onclick = function () {
   
   
            ul.removeChild(li);
            Observer.fire('减少操作', {
   
    count: -1 });
        };
        // 添加消息内容
        li.innerText = msg;
        // 添加删除按钮
        li.appendChild(btn);
        // 追加一则消息
        ul.appendChild(li);
    }

    // 注册这个功能
    Observer.regist('新增操作', addMsgItem);
}());

/**
 * 工程师 B
 *      实现了消息数目累加减功能
 */
; (function () {
   
   
    /**
     * 修改消息数量
     * @param {number} count 累加减数量 -1和1
     */
    function changeMsgCount({
   
    count }) {
   
   
        console.log(count);
        {
   
   mathJaxContainer[1]}('msg_num').innerText) + count;
    }

    // 分类别注册这个功能(新增的操作作为一个分类,减少的操作作为一个类别)
    Observer.regist('新增操作', changeMsgCount)
        .regist('减少操作', changeMsgCount);
}());

/**
 * 工程师 C
 *      实现了消息提交的功能
 */
; (function () {
   
   
    /**
     * 用户点击提交按钮
     */
    $('user_submit').onclick = function () {
   
   
        // 获取用户输入框中输入的信息
        const msg = $('user_input');
        // 如果输入框内容为空则退出函数
        if (msg.value === '') return;
        Observer.fire('新增操作', {
   
    msg: msg.value, count: 1 });
        // 点击完成后将输入框置为空
        msg.value = '';
    };
}());

console.log(Observer);

理解:

将函数进行分类并缓存成队列(订阅),然后当需要执行指定操作时依次调用同分类的函数方法(发布)。

  • 此模块的实现:
    • regist
      • 订阅 新增类 函数方法(包括发送消息递增消息数目);
        • 发送消息: Observer.regist('新增操作', addMsgItem);
        • 递增消息数目: Observer.regist('新增操作', changeMsgCount);
      • 订阅 减法类 函数方法(包括递减消息数目);
        • 递减消息数目: Observer.regist('减少操作', changeMsgCount);
    • fire
      • 发布 订阅的 新增类 函数方法
        • Observer.fire('新增操作', { msg: msg.value, count: 1 });
      • 发布 订阅的 减法类 函数方法
        • Observer.fire('减少操作', { count: -1 });

对象间解耦

  • 需求(考试):
    1. 学生考试获得最终的考试结果;
    2. 学生也可以放弃某一学科的考试;
    3. 教师发布考试最终的结果表;

image.png

/**
 * 定义观察者
 */
const Observer = (function () {
   
   
    // 防止消息队列暴漏而被篡改故将消息容器作为静态私有变量保存
    let __messages = {
   
   };
    return {
   
   
        __messages,
        /**
         * 注册消息接口 - 暂存
         * @param {string} type 消息类型
         * @param {Function} fn 消息回调
         */
        regist(type, fn) {
   
   
            // 如果此消息不存在则应该创建一个该消息类型
            if (typeof __messages[type] === 'undefined') {
   
   
                // 将动作推入到该消息对应的动作执行队列中
                __messages[type] = [fn];
            }
            // 如果此消息存在
            else {
   
   
                // 将动作方法推入该消息对应的动作执行序列中
                __messages[type].push(fn);
            }
            return this;
        },
        /**
         * 发布消息接口 - 调用
         * @param {string} type 消息类型
         * @param {object} args 消息携带数据
         */
        fire(type, args) {
   
   
            // 如果该消息没有被注册,则返回
            if (!__messages[type]) return;
            // 遍历消息动作
            for (let i = 0; i < __messages[type].length; i++) {
   
   
                // 依次执行注册的消息对应的动作序列
                __messages[type][i].call(this, args);
            }
        },
        /**
         * 移除消息接口 - 移除
         * @param {string} type 消息类型
         * @param {Function} fn 消息回调
         */
        remove(type, fn) {
   
   
            // 如果消息动作队列存在
            if (__messages[type] instanceof Array) {
   
   
                // 从最后一个消息动作遍历
                for (let i = __messages[type].length - 1; i >= 0; i--) {
   
   
                    // 如果存在该动作则在消息动作序列中移除相应动作
                    __messages[type][i] === fn && __messages[type].splice(i, 1);
                }
            }
        }
    };
}());
/**
 * 学生类 订阅者
 * @param {string} name 姓名
 */
const Student = function (name) {
   
   
    this.name = name;
    this.getResult = () => console.log(`${this.name} 的结果为:${this.result} 分!`);
};
Student.prototype = {
   
   
    /**
     * 回答的试卷
     * @param {string} test 试卷
     * @param {number} result 结果
     */
    setResult(test, result) {
   
   
        this.result = result;
        Observer.regist(test, this.getResult);
    },
    /**
     * 放弃方法
     * @param {string} test 试卷
     */
     giveUp(test) {
   
   
        console.log(`${this.name} 放弃了 ${test} ~~~`);
        Observer.remove(test, this.getResult);
    }
};

/**
 * 教师类 发布者
 */
const Teacher = function () {
   
    };
Teacher.prototype = {
   
   
    // 发布试卷
    distributeTest(test) {
   
   
        console.log(`发布 ${
     
     test} 的结果:`);
        // 发布试卷
        Observer.fire(test)
    }
};
const s1 = new Student('Tom');
const s2 = new Student('Lee');
const s3 = new Student('张三');

// s1 考了试卷1、2,最终又放弃了试卷2
s1.setResult('试卷1', 10);
s1.setResult('试卷2', 20);
s1.giveUp('试卷2');

// s2 考了试卷2、3,放弃了试卷1
s2.giveUp('试卷1');
s2.setResult('试卷2', 80);
s2.setResult('试卷3', 100);

// s3 考了试卷1、2、3
s3.setResult('试卷1', 70);
s3.setResult('试卷2', 50);
s3.setResult('试卷3', 30);

console.log('=============');

const teacher = new Teacher();

teacher.distributeTest('试卷1');
console.log('=============');

teacher.distributeTest('试卷2');
console.log('=============');

teacher.distributeTest('试卷3');
console.log('=============');

console.log(Observer);
目录
相关文章
|
15天前
|
设计模式 监控 Java
Kotlin教程笔记(52) - 改良设计模式 - 观察者模式
Kotlin教程笔记(52) - 改良设计模式 - 观察者模式
|
1月前
|
设计模式 传感器
【设计模式】观察者模式(定义 | 特点 | Demo入门讲解)
【设计模式】观察者模式(定义 | 特点 | Demo入门讲解)
39 0
|
10天前
|
设计模式 监控 Java
Kotlin教程笔记(52) - 改良设计模式 - 观察者模式
Kotlin教程笔记(52) - 改良设计模式 - 观察者模式
21 3
|
22天前
|
设计模式 监控 Java
Kotlin教程笔记(52) - 改良设计模式 - 观察者模式
Kotlin教程笔记(52) - 改良设计模式 - 观察者模式
33 9
|
21天前
|
设计模式 监控 Java
Kotlin教程笔记(52) - 改良设计模式 - 观察者模式
Kotlin教程笔记(52) - 改良设计模式 - 观察者模式
27 2
|
30天前
|
设计模式 监控 UED
设计模式之观察者模式
【10月更文挑战第12天】 观察者模式是一种行为型设计模式,定义了一对多的依赖关系,当一个对象状态改变时,所有依赖它的对象都会自动更新。主要由主题(被观察者)和观察者组成,实现对象间的松耦合,广泛应用于用户界面、事件驱动系统和数据监控等领域。
|
1月前
|
设计模式 监控 Java
Kotlin教程笔记(52) - 改良设计模式 - 观察者模式
本教程详细讲解Kotlin语法,适合深入学习。对于快速掌握Kotlin,推荐“简洁”系列教程。本文特别介绍了观察者模式,包括使用Java API和Kotlin委托属性(如Delegates.observable)实现的方法,旨在帮助开发者更高效地实现和优化观察者模式的应用。
32 3
|
1月前
|
设计模式 JavaScript 前端开发
JavaScript设计模式--访问者模式
【10月更文挑战第1天】
31 3
|
2月前
|
设计模式 Java 关系型数据库
设计模式——观察者模式
观察者模式介绍、观察者模式优化天气预报案例、JDK 的Observable类和Observer类
设计模式——观察者模式
|
1月前
|
JavaScript 前端开发
前端js,vue系统使用iframe嵌入第三方系统的父子系统的通信
前端js,vue系统使用iframe嵌入第三方系统的父子系统的通信