解决模块间耦合的设计模式
大家都知道,模块间的耦合不利于代码的可读性和可维护性,好的代码一般都会按功能或者别的将代码模块化,那么模块化的代码之间怎么通信呢?下面来介绍两种用于模块间解耦的设计模式。
观察者模式
概念介绍
观察者模式也被称作消息机制或发布-订阅者模式,为了解决主题对象与观察者之间功能的耦合。
观察者模式有一个消息容器,和三个方法,分别是订阅信息方法、取消订阅的信息方法、发送订阅的消息方法。
例如:
/* * 将观察者放在闭包中,当页面加载就立即执行 */ const Observer = (function () { // 防止消息队列暴露而被篡改故将消息容器作为静态私有变量保存 let _messages = {}; return { // 注册信息接口 register() { }, // 发布信息接口 fire() { }, // 移除信息接口 remove() { } } })();
方法实现
注册方法的作用是将订阅者注册的消息推入到消息队列中。
接受两个参数:消息类型以及相应的处理动作。
代码如下:
// 注册信息接口 register(type, fn) { // 如果此消息不存在则应该创建一个该消息类型 if(typeof _messages[type] === 'undefined') { // 将动作推入到该消息对应的动作执行队列中 _messages[type] = [fn]; // 如果此消息存在 } else { // 将动作方法推入该消息对应的动作执行序列中 _messages[type].push(fn); } }
发布消息方法,其功能是当观察者发布一个消息时将所有订阅者订阅的消息一次执行。
接收两个参数:消息类型及动作执行时需要传递的参数。
// 发布信息接口 fire(type, args) { // 如果该消息没有被注册,则返回 if(!_messages[type]) { return; } // 定义消息信息 const events = { type: type, // 消息类型 args: args || {} // 消息携带数据 }; // 遍历消息动作 for(let i = 0, len = _messages[type].length; i < len; i++) { // 依次执行注册的消息对应的动作序列 _messages[type][i].call(this, events); } }
消息注销方法:将订阅者注销的消息从消息队列中清除。
接收两个参数:消息类型及执行某一动作。
// 移除信息接口 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); } } }
整体代码如下:
/* * 将观察者放在闭包中,当页面加载就立即执行 */ const Observer = (function () { // 防止消息队列暴露而被篡改故将消息容器作为静态私有变量保存 let _messages = {}; return { // 注册信息接口 register(type, fn) { // 如果此消息不存在则应该创建一个该消息类型 if(typeof _messages[type] === 'undefined') { // 将动作推入到该消息对应的动作执行队列中 _messages[type] = [fn]; // 如果此消息存在 } else { // 将动作方法推入该消息对应的动作执行序列中 _messages[type].push(fn); } }, // 发布信息接口 fire(type, args) { // 如果该消息没有被注册,则返回 if(!_messages[type]) { return; } // 定义消息信息 const events = { type: type, // 消息类型 args: args || {} // 消息携带数据 }; // 遍历消息动作 for(let i = 0, len = _messages[type].length; i < len; i++) { // 依次执行注册的消息对应的动作序列 _messages[type][i].call(this, events); } }, // 移除信息接口 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); } } } } })();
测试实例如下:
// 测试实例 function register(event) { console.log(event.type, event.args.msg); } Observer.register('test', register); Observer.fire('test', { msg: '传递参数' }); Observer.remove('test',register); Observer.fire('test', { msg: '传递参数' });
使用场景
当用户发布评论时,会在评论展示模块末尾处追加新的评论,与此同时用户的消息模块的消息数量也会递增。如果用户删除留言区的信息时,用户的消息模块消息数量也会递减。
其中,评论展示模块,消息模块,评论模块是三个独立的模块。
实现如下:
评论展示模块:
(function(){ /** * 追加一则消息 * @param e */ function addMsgItem(e) { let text = e.args.text, // 获取消息中用户添加的文本内容 ul = $('msg'), // 留言容器元素 li = document.createElement('li'), // 创建内容容器元素 span = document.createElement('span'); // 删除按钮 li.innerHTML = text; // 写入评论 // 关闭按钮 span.onclick = function() { ul.removeChild(li); // 发布删除留言消息 Observer.fire('removeCommentMessage',{ num: -1 }); }; // 添加删除按钮 ul.appendChild(li); // 添加留言节点 ul.appendChild(li); } // 注册添加评论信息 Observer.register('addCommentMessage', addMsgItem); })();
消息模块:
(function() { /** * 更改用户消息数目 * @param e */ function changeMseNum(e) { // 获取需要增加的用户消息数目 let num = e.args.num; const $msgNum = $('msgNum'); // 增加用户消息数目并写入页面中 $msgNum.innerHTML = parseInt($msgNum.innerHTML) + num; } // 注册添加评论信息 Observer.register('addCommentMessage',changeMseNum); Observer.register('removeCommentMessage', changeMseNum); })();
评论模块:
(function(){ // 用户点击提交按钮 $('useSubmit').onclick = function() { // 获取用户输入框中输入的信息 const text = $('useInput'); // 如果消息为空则提交失败 if(!text.value) { return; } // 发布一则评论信息 Observer.fire('addCommentMessage',{ text: text.value, // 消息评论内容 num: 1 // 消息评论数目 }); text.value = ''; } })();
demo地址:http://jsrun.net/PDYKp/show
观察者模式也适用于对象间的解耦,代码如下:
// 对象间的解耦 class Student { constructor(result) { const self = this; self.result = result; self.say = function() { console.log(self.result); } } answer(question) { // 注册参数问题 Observer.register(question,this.say); } sleep(question) { console.log(`${this.result} ${question}已被注销`); // 取消对老师问题对监听 Observer.remove(question, this.say); } } class Teacher { constructor() { } ask(question) { console.log(`问题是 ${question}`); // 发布提问消息 Observer.fire(question); } } // 实例 const student1 = new Student('学生1回答问题'); const student2 = new Student('学生2回答问题'); const student3 = new Student('学生3回答问题'); const teacher = new Teacher(); // 学生监听老师提问问题 student1.answer('什么是设计模式?'); student1.answer('简述观察者模式'); student2.answer('什么是设计模式?'); student3.answer('什么是设计模式?'); student3.answer('简述观察者模式'); // 老师提问问题 teacher.ask('什么是设计模式?'); // 学生3在'简述观察者模式的时候睡着了' student3.sleep('简述观察者模式'); teacher.ask('简述观察者模式');
运行结果如下:
中介者模式
概念和实现
中介者模式通过模块间或者对象间的复杂通信,来解决模块或对象间的耦合。
本质是分装多个对象的交互,并且这些对象的交互一般都是在中介者内部实现的。
代码实现如下:
/** * 中介者对象 */ const Mediator = (function() { // 消息对象 let _msg = {}; return { /** * 订阅消息方法 * @param type 消息名称 * @param action 消息回调函数 */ register(type, action) { // 如果消息存在 if(_msg[type]) { // 存入回调函数 _msg[type].push(action); } else { // 不存在,则建立该消息容器 _msg[type] = [action]; } }, /** * 发布消息方法 * @param type 消息名称 */ send(type) { // 如果该消息已经被订阅 if(_msg[type]) { // 遍历已存储的消息回调函数 for(let i = 0, len = _msg[type].length; i < len; i++) { // 执行该回调函数 _msg[type][i] && _msg[type][i](); } } } } })();
测试代码如下:
// 订阅demo消息,执行回调函数--输出first Mediator.register('demo',()=> { console.log('first'); }); // 订阅demo消息,执行回调函数--输出second Mediator.register('demo', ()=> { console.log('second'); }); // 发布demo消息 Mediator.send('demo');
使用场景
添加一个用户首页导航设置,导航上面有消息提醒和导航网址。具体如下:
用户收藏导航模块有消息提醒和导航网址功能;
推荐用户导航有推荐用户导航内的导航有消息提醒功能;
最近常用导航有最近常用导航栏内有导航网址功能;
代码实现:
用户收藏导航模块
(function(){ // 订阅隐藏用户收藏导航消息提醒消息 Mediator.register('hideAllNavNum', () => { showHideNavWidget('collectionNav', 'b', false); }); // 订阅显示用户收藏导航消息提醒消息 Mediator.register('showAllNavNum', () => { showHideNavWidget('collectionNav', 'b', true); }); // 订阅隐藏用户收藏导航消息提醒消息 Mediator.register('hideAllNavUrl', () => { showHideNavWidget('collectionNav', 'span', false); }); // 订阅显示用户收藏导航消息提醒消息 Mediator.register('showAllNavUrl', () => { showHideNavWidget('collectionNav', 'span', true); }); })();
推荐用户导航
(function() { // 订阅隐藏推荐用户导航消息提醒消息 Mediator.register('hideAllNavNum', () => { showHideNavWidget('recommendNav', 'b', false); }); // 订阅显示推荐用户导航消息提醒消息 Mediator.register('showAllNavNum', () => { showHideNavWidget('recommendNav', 'b', true); }); })();
最近常用导航
(function() { // 订阅隐藏最近常用导航消息 Mediator.register('hideAllNavUrl', () => { showHideNavWidget('recentlyNav', 'span', 'hide'); }); // 订阅显示最近常用导航消息 Mediator.register('showAllNavUrl', () => { showHideNavWidget('recentlyNav', 'span', 'show'); }); })();
设置层模块
(function () { // 消息提醒选框 const hideNum = document.getElementById('hideNum'); // 网址选框 const hideUrl = document.getElementById('hideUrl'); // 消息提醒选框事件 hideNum.addEventListener('change',function(){ if(this.checked){ // 中介者发布隐藏消息提醒功能消息 Mediator.send('hideAllNavNum'); } else { // 中介者发布隐藏消息提醒功能消息 Mediator.send('showAllNavNum'); } },false); // 网址选框事件 hideUrl.addEventListener('change',function() { if(hideUrl.checked) { // 中介者发布隐藏所有网址功能消息 Mediator.send('hideAllNavUrl'); } else { // 中介者发布显示所有网址功能消息 Mediator.send('showAllNavUrl'); } }, false); })();
demo地址:http://jsrun.net/QDYKp/show
总结
观察者模式最主要的作用是解决类和对象之间的耦合,解耦两个相互依赖的对象,使其依赖于观察者的消息机制。对于任意一个订阅者对象来说,其他订阅者对象的改变不会影响到自身。对于每一个订阅者来说,其自身既可以是消息的发出者也可以是消息的执行者。
中介者模式中的消息的发送方只有一个,就是中介者对象,中介者对象不能订阅消息,只有那些活跃对象(订阅者)才可以订阅中介者的消息。也可以将中介者模式看作将消息系统封装在中介者对象内部,中介者对象只能是消息的发送者。
中介者模式与观察者模式比较:
都是通过消息传递实现对象间或模块间的解耦。
观察者中的订阅者是双向的,既可以是消息的发布者,也可以是消息的订阅者。
中介者模式中,订阅者是单项的,只能是消息的订阅,消息同一有中介者对象发布,所有订阅者对象间接地被中介者管理。