发布者订阅者模式,是一种很常见的模式,比如:

一、买卖房子

生活中的买房,卖房,中介就构成了一个发布订阅者模式,买房的人,一般需要的是房源,价格,使用面积等信息,他充当了订阅者的角色

中介拿到卖主的房源信息,根据手头上掌握的客户联系信息(买房的人的手机号),通知买房的人,他充当了发布者的角色

卖主想卖掉自己的房子,就需要告诉中介,把信息交给中介发布

二,网站订阅信息的用户

订阅者角色:需要订阅某类信息的网民,如某个网站的javascript类型文章

发布者角色:邮箱服务器,根据网站收集到的用户订阅邮箱,通知用户.

网站主想把信息告诉订阅者,需要把文章相关内容告诉邮箱服务器去发送

等等非常多的例子,不一一列举

本文用网站订阅的方式,推导发布者-订阅者框架,然后用发布者-订阅者框架来重构一个简单的购物车

         var Site = {};
Site.userList = [];
Site.subscribe = function( fn ){
this.userList.push( fn );
}
Site.publish = function(){
for( var i = 0, len = this.userList.length; i < len; i++ ){
this.userList[i].apply( this, arguments );
}
}
Site.subscribe( function( type ){
console.log( "网站发布了" + type + "内容" );
});
Site.subscribe( function( type ){
console.log( "网站发布了" + type + "内容" );
});
Site.publish( 'javascript' );
Site.publish( 'html5' );

Site.userList就是用来保存订阅者

Site.subscribe就是具体的订阅者,把每一个订阅者订阅的具体信息保存在Site.userList

Site.publish就是发布者:根据保存的userList,一个个遍历(通知),执行里面的业务逻辑

但是这个,发布订阅者模式,有个问题,不能订阅想要的类型,上例我加了2个订阅者(第11行,第14行),只要网站发了信息,全部能收到,但是有些用户可能只想收到javascript或者html5的,所以,接下来,我们需要继续完善,希望能够接收到具体的信息,不是某人订阅的类型,就不接收

         var Site = {};
Site.userList = {};
Site.subscribe = function (key, fn) {
if (!this.userList[key]) {
this.userList[key] = [];
}
this.userList[key].push(fn);
}
Site.publish = function () {
var key = Array.prototype.shift.apply(arguments),
fns = this.userList[key];
if ( !fns || fns.length === 0) {
console.log( '没有人订阅' + key + "这个分类的文章" );
return false;
}
for (var i = 0, len = fns.length; i < len; i++) {
fns[i].apply(this, arguments);
}
} Site.subscribe( "javascript", function( title ){
console.log( title );
}); Site.subscribe( "es6", function( title ){
console.log( title );
}); Site.publish( "javascript", "[js高手之路]寄生组合式继承的优势" );
Site.publish( "es6", "[js高手之路]es6系列教程 - var, let, const详解" );
Site.publish( "html5", "html5新的语义化标签" );

输出结果:

[js高手之路]寄生组合式继承的优势

[js高手之路]es6系列教程 - var, let, const详解

没有人订阅html5这个分类的文章

我们可以看到,只有订阅了javascript类型文章的人,才能收到 ”寄生组合式继承的优势” 这篇文章,发布html5类型的时候,没有任何人会收到.

es6类型的,只有订阅es6的人,才能收到

我们已经有了一个基本的发布订阅者框架,接下来,把他完善成一个框架,便于其他功能或者其他网站系统的相同功能可以重用他

        var Event = {
userList : {},
subscribe : function (key, fn) {
if (!this.userList[key]) {
this.userList[key] = [];
}
this.userList[key].push(fn);
},
publish : function () {
var key = Array.prototype.shift.apply(arguments),
fns = this.userList[key];
if (!fns || fns.length === 0) {
console.log('没有人订阅' + key + "这个分类的文章");
return false;
}
for (var i = 0, len = fns.length; i < len; i++) {
fns[i].apply(this, arguments);
}
}
}; var extend = function( dstObj, srcObj ){
for( var key in srcObj ){
dstObj[key] = srcObj[key];
}
} var Site = {};
extend( Site, Event );
Site.subscribe( "javascript", function( title ){
console.log( title );
}); Site.subscribe( "es6", function( title ){
console.log( title );
}); Site.publish( "javascript", "寄生组合式继承的优势" );
Site.publish( "es6", "es6系列教程 - var, let, const详解" );
Site.publish( "html5", "html5新的语义化标签" );

然后,我们来重构一个购物车实例,没有重构之前,我的购物车用的是面向过程:

 <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="js/cart.js"></script>
</head>
<body>
<div id="box">
<ul>
<li>
<input type="button" value="-">
<span class="num">0</span>
<input type="button" value="+">
<span>单价:</span>
<span class="unit">15元;</span>
<span class="label">小计:</span>
<span class="subtotal">0</span>元
</li>
<li>
<input type="button" value="-">
<span class="num">0</span>
<input type="button" value="+">
<span>单价:</span>
<span class="unit">10元;</span>
<span class="label">小计:</span>
<span class="subtotal">0</span>元
</li>
<li>
<input type="button" value="-">
<span class="num">0</span>
<input type="button" value="+">
<span>单价:</span>
<span class="unit">5元;</span>
<span class="label">小计:</span>
<span class="subtotal">0</span>元
</li>
<li>
<input type="button" value="-">
<span class="num">0</span>
<input type="button" value="+">
<span>单价:</span>
<span class="unit">2元;</span>
<span class="label">小计:</span>
<span class="subtotal">0</span>元
</li>
<li>
<input type="button" value="-">
<span class="num">0</span>
<input type="button" value="+">
<span>单价:</span>
<span class="unit">1元;</span>
<span class="label">小计:</span>
<span class="subtotal">0</span>元
</li>
</ul>
<div class="total-box">
商品一共
<span id="goods-num">0</span>
件;
一共花费
<span id="total-price">0</span>
元;
其中最贵的商品单价是<span id="unit-price">0</span>元
</div>
</div>
</body>
</html>

cart.js文件:

 function getByClass(cName, obj) {
var o = null;
if (arguments.length == 2) {
o = obj;
} else {
o = document;
}
var allNode = o.getElementsByTagName("*");
var aNode = [];
for( var i = 0 ; i < allNode.length; i++ ){
if( allNode[i].className == cName ){
aNode.push( allNode[i] );
}
}
return aNode;
} function getSubTotal( unitPrice, goodsNum ){
return unitPrice * goodsNum;
} function getSum(){ //计算总花费
var aSubtotal = getByClass("subtotal");
var res = 0;
for( var i = 0; i < aSubtotal.length; i++ ){
res += parseInt(aSubtotal[i].innerHTML);
}
return res;
} function compareUnit() { //比单价,找出最高的单价
var aNum = getByClass( "num");
var aUnit = getByClass( "unit");
var temp = 0;
for( var i = 0; i < aNum.length; i++ ){
if( parseInt(aNum[i].innerHTML) != 0 ){
if( temp < parseInt(aUnit[i].innerHTML) ) {
temp = parseInt(aUnit[i].innerHTML);
}
}
}
return temp;
} window.onload = function () {
var aInput = document.getElementsByTagName("input");
var total = 0;
var oGoodsNum = document.getElementById("goods-num");
var oTotalPrice = document.getElementById("total-price");
var oUnitPrice = document.getElementById("unit-price"); for (var i = 0; i < aInput.length; i++) {
if (i % 2 != 0) { //加号
aInput[i].onclick = function () {
//当前加号所在行的数量
var aNum = getByClass( "num", this.parentNode );
var n = parseInt( aNum[0].innerHTML );
n++;
aNum[0].innerHTML = n;
//获取单价
var aUnit = getByClass( "unit", this.parentNode );
var unitPrice = parseInt(aUnit[0].innerHTML);
var subtotal = getSubTotal( unitPrice, n );
var aSubtotal = getByClass( "subtotal", this.parentNode );
aSubtotal[0].innerHTML = subtotal;
total++; //商品总数
oGoodsNum.innerHTML = total;
oTotalPrice.innerHTML = getSum();
oUnitPrice.innerHTML = compareUnit();
}
}else {
aInput[i].onclick = function(){
var aNum = getByClass( "num", this.parentNode );
if ( parseInt( aNum[0].innerHTML ) != 0 ){
var n = parseInt( aNum[0].innerHTML );
n--;
aNum[0].innerHTML = n;
//获取单价
var aUnit = getByClass( "unit", this.parentNode );
var unitPrice = parseInt(aUnit[0].innerHTML);
var subtotal = getSubTotal( unitPrice, n );
var aSubtotal = getByClass( "subtotal", this.parentNode );
aSubtotal[0].innerHTML = subtotal;
total--; //商品总数
oGoodsNum.innerHTML = total;
oTotalPrice.innerHTML = getSum();
oUnitPrice.innerHTML = compareUnit();
}
}
}
}
}

耦合度太高,可维护性很差.

重构之后的购物车:

 window.onload = function () {
var Event = {
userList: {},
subscribe: function (key, fn) {
if (!this.userList[key]) {
this.userList[key] = [];
}
this.userList[key].push(fn);
},
publish: function () {
var key = Array.prototype.shift.apply(arguments),
fns = this.userList[key];
if (!fns || fns.length === 0) {
return false;
}
for (var i = 0, len = fns.length; i < len; i++) {
fns[i].apply(this, arguments);
}
}
};
(function(){
var aBtnMinus = document.querySelectorAll( "#box li>input:first-child"),
aBtnPlus = document.querySelectorAll( "#box li>input:nth-of-type(2)"),
curNum = 0, curUnitPrice = 0; for( var i = 0, len = aBtnMinus.length; i < len; i++ ){
aBtnMinus[i].index = aBtnPlus[i].index = i;
aBtnMinus[i].onclick = function(){
(this.parentNode.children[1].innerHTML > 0) && Event.publish( "total-goods-num-minus" );
--this.parentNode.children[1].innerHTML < 0 && (this.parentNode.children[1].innerHTML = 0);
curUnitPrice = this.parentNode.children[4].innerHTML;
Event.publish( "minus-num" + this.index,
parseInt( curUnitPrice ),
parseInt( this.parentNode.children[1].innerHTML )
);
};
aBtnPlus[i].onclick = function(){
(this.parentNode.children[1].innerHTML >= 0) && Event.publish( "total-goods-num-plus" );
this.parentNode.children[1].innerHTML++;
curUnitPrice = this.parentNode.children[4].innerHTML;
Event.publish( "plus-num" + this.index,
parseInt( curUnitPrice ),
parseInt( this.parentNode.children[1].innerHTML )
);
}
}
})();
(function(){
var aSubtotal = document.querySelectorAll("#box .subtotal"),
oGoodsNum = document.querySelector("#goods-num"),
oTotalPrice = document.querySelector("#total-price");
Event.subscribe( 'total-goods-num-plus', function(){
++oGoodsNum.innerHTML;
});
Event.subscribe( 'total-goods-num-minus', function(){
--oGoodsNum.innerHTML;
});
for( let i = 0, len = aSubtotal.length; i < len; i++ ){
Event.subscribe( 'minus-num' + i, function( unitPrice, num ){
aSubtotal[i].innerHTML = unitPrice * num;
});
Event.subscribe( 'plus-num' + i, function( unitPrice, num ){
aSubtotal[i].innerHTML = unitPrice * num;
});
}
})();
console.log( Event.userList );
}

[js高手之路]设计模式系列课程-发布者,订阅者重构购物车的更多相关文章

  1. [js高手之路] 设计模式系列课程 - jQuery的extend插件机制

    这里在之前的文章[js高手之路] 设计模式系列课程 - jQuery的链式调用与灵活的构造函数基础上增加一个extend浅拷贝,可以为对象方便的扩展属性和方法, jquery的插件扩展机制,大致就是这 ...

  2. [js高手之路]设计模式系列课程-组合模式+寄生组合继承实战新闻列表

    所谓组合模式,就是把一堆结构分解出来,组成在一起,现实中很多这样的例子,如: 1.肯德基套餐就是一种组合模式, 比如鸡腿堡套餐,一般是是由一个鸡腿堡,一包薯条,一杯可乐等组成的 2.组装的台式机同理, ...

  3. [js高手之路] 设计模式系列课程 - jQuery的链式调用与灵活的构造函数

    一.我们从一个简单的构造函数+原型程序开始 var G = function(){}; G.prototype = { length : 5, size : function(){ return th ...

  4. [js高手之路]设计模式系列课程-单例模式实现模态框

    什么是单例呢? 单,就是一个的意思.例:就是实例化出来的对象,那合在一起就是保证一个构造函数只能new出一个实例,为什么要学习单例模式呢?或者说单例模式有哪些常见的应用场景.它的使用还是很广泛,比如: ...

  5. [js高手之路]设计模式系列课程-设计一个模块化扩展功能(define)和使用(use)库

    模块化的诞生标志着javascript开发进入工业时代,近几年随着es6, require js( sea js ), node js崛起,特别是es6和node js自带模块加载功能,给大型程序开发 ...

  6. [js高手之路]设计模式系列课程-委托模式实战微博发布功能

    在实际开发中,经常需要为Dom元素绑定事件,如果页面上有4个li元素,点击对应的li,弹出对应的li内容,怎么做呢?是不是很简单? 大多数人的做法都是:获取元素,绑定事件 <ul> < ...

  7. [js高手之路] 设计模式系列课程 - DOM迭代器(2)

    如果你对jquery比较熟悉的话,应该用过 eq, first, last, get, prev, next, siblings等过滤器和方法.本文,我们就用迭代设计模式来封装实现,类似的功能 < ...

  8. [js高手之路] 设计模式系列课程 - 迭代器(1)

    迭代器是指通过一种形式依次遍历数组,对象,或者类数组结构中的每个元素. 常见的有jquery中的each方法, ES5自带的forEach方法. 下面我们就来自定义一个类似jquery或者ES5的迭代 ...

  9. [js高手之路] es6系列教程 - 迭代器与生成器详解

    什么是迭代器? 迭代器是一种特殊对象,这种对象具有以下特点: 1,所有对象都有一个next方法 2,每次调用next方法,都会返回一个对象,该对象包含两个属性,一个是value, 表示下一个将要返回的 ...

随机推荐

  1. 求n个逆元的O(n)算法

    它的推导过程如下,设,那么 对上式两边同时除,进一步得到 再把和替换掉,最终得到 初始化,这样就可以通过递推法求出模奇素数的所有逆元了. 转自  http://blog.csdn.net/acdrea ...

  2. Java用Cookie简单限制点赞次数

    楼主最近在搞一个当下比较流行的点赞功能,这个功能也是让程序员又爱又恨啊 说起爱,点赞是个社会化的动作,全民都在为美好的事情,行为,动作,点赞. 说起恨,你很难在用户没有登录的情况下限制恶意点赞的机器人 ...

  3. Oracle 11g OCM 考试大纲

    考试大纲共分9部分.   一.Server Configuration 服务器配置 1  Create the database 创建数据库 2  Determine and set sizing p ...

  4. SSH连不上虚拟机的问题解决

    这几天工作任务不重,今早上班打算在liunx下运行下python脚本.打开VM,SSH突然连不上虚拟机了.网上试了很多方法都不行,最后花费2个小时解决了这一问题. 结合我的实际情况,问题解决如下: 1 ...

  5. new/delete 与 malloc/free的区别

    一.概述     在C++中,申请动态内存与释放动态内存用new/delete 与 malloc/free都可以,而且他们的存储方式相同,new/malloc申请的动态内存位于堆中,无法被操作系统自动 ...

  6. SpringMVC配置实例

    一.SpringMVC概述 MVCII模式实现的框架技术 Model--业务模型(Biz,Dao...) View--jsp及相关的jquery框架技术(easyui) Contraller--Dis ...

  7. Java开源博客My-Blog(SpringBoot+Docker)系列文章

    My Blog 1.Docker+SpringBoot+Mybatis+thymeleaf的Java博客系统开源啦 2.My-Blog搭建过程:如何让一个网站从零到可以上线访问 3.将数据的初始化放到 ...

  8. maven项目打包发布时跳过测试

    mvn命令: mvn clean install -Dmaven.test.skip=true eclipse build... 命令: clean install -Dmaven.test.skip ...

  9. 平方根的C语言实现(一)

    曾经做一个硬件成本极度控制的项目,因为硬件成本极低,并且还需要实现较高的精度测量,过程中也自己用C语言实现了正弦.余弦.反正切.平方根等函数. 以下,无论是在我的实际项目中还是本地的计算机系统,int ...

  10. 机器学习 —— 基础整理(三)生成式模型的非参数方法: Parzen窗估计、k近邻估计;k近邻分类器

    本文简述了以下内容: (一)生成式模型的非参数方法 (二)Parzen窗估计 (三)k近邻估计 (四)k近邻分类器(k-nearest neighbor,kNN) (一)非参数方法(Non-param ...