Javascript乱弹设计模式系列(1) - 观察者模式(Observer)
博客園談設(shè)計(jì)模式的文章很多,我也受益匪淺,包括TerryLee、呂震宇等等的.NET設(shè)計(jì)模式系列文章,強(qiáng)烈推薦。對(duì)于我,擅長(zhǎng)于前臺(tái)代碼的開發(fā),對(duì)于設(shè)計(jì)模式也有一定的了解,于是我想結(jié)合Javascript來(lái)設(shè)計(jì)前臺(tái)方面的“設(shè)計(jì)模式”,以對(duì)后臺(tái)“設(shè)計(jì)模式”做個(gè)補(bǔ)充。開始這個(gè)系列我也誠(chéng)惶誠(chéng)恐,怕自己寫得不好,不過(guò)我也想做個(gè)嘗試,一來(lái)希望能給一些人有些幫助吧,二來(lái)從寫文章中鍛煉下自己,三來(lái)通過(guò)寫文章對(duì)自己增加自信;如果寫得不好,歡迎拍磚,我會(huì)虛心向博客園高手牛人們學(xué)習(xí)請(qǐng)教;如果覺得寫得還可以,謝謝大家的支持了:)
這篇將介紹觀察者模式。
概述
在現(xiàn)實(shí)生活中,存在著“通知依賴關(guān)系”,如在報(bào)紙訂閱的服務(wù),只要讀者(訂閱者)訂購(gòu)了《程序員》的期刊雜志,那么他就訂閱了這個(gè)服務(wù),他時(shí)刻“監(jiān)聽”著郵遞員(出版者)來(lái)投遞報(bào)紙給他們,而郵遞員(出版者)只要報(bào)社有新刊雜志傳達(dá)給他(就是狀態(tài)發(fā)生了變化),郵遞員(出版者)就隨時(shí)投遞(通知)訂閱了服務(wù)的讀者;另一方面,如果讀者不想在繼續(xù)訂購(gòu)(取消通知)《程序員》的雜志了,那么郵遞員就不在投遞(通知)這些讀者了。---這就是典型的出版者和訂閱者的關(guān)系,而這個(gè)關(guān)系用一個(gè)公式來(lái)概括:
出版者 + 訂閱者 = 觀察者模式
定義
觀察者模式定義了對(duì)象之間的一對(duì)多依賴,這樣一來(lái),當(dāng)一個(gè)對(duì)象改變狀態(tài)時(shí),他的所有依賴者都會(huì)收到通知并自動(dòng)更新。
類圖
示例分析
現(xiàn)在開始利用觀察者模式來(lái)應(yīng)用到項(xiàng)目的一個(gè)場(chǎng)景中去,并且層層剖析一下。
有這樣一個(gè)場(chǎng)景,一個(gè)購(gòu)書網(wǎng)站,用戶提交上去一個(gè)訂單,網(wǎng)站系統(tǒng)只要有訂單上來(lái),會(huì)采取如下操作:(為了簡(jiǎn)化,我這里其實(shí)只是簡(jiǎn)單的提交)
一、產(chǎn)生一條通知用戶“已購(gòu)買”記錄的短信息(該短信箱還會(huì)有其他記錄,如交友等等);
二、在瀏覽器上顯示你的訂單名片;
三、該條訂單提交上服務(wù)器,保存到數(shù)據(jù)庫(kù)或者其它任何存儲(chǔ)介質(zhì)中去,最后顯示你的購(gòu)書記錄;
那么開始我的設(shè)計(jì):
1. 網(wǎng)站上添加IPublisher.js,它作為系統(tǒng)的出版者“接口”,利用第0篇文章面向?qū)ο蠡A(chǔ)以及接口和繼承類的實(shí)現(xiàn)中的Interface.js類(另外,謝謝winter-cn園友提出了些寶貴的建議,目前Interface類還在改善中,這里暫且先用原來(lái)的Interface類:
這里是改進(jìn)的程序示范,包括重載函數(shù)的構(gòu)造,這里也暫時(shí)貼出來(lái)下:
改進(jìn)的代碼改進(jìn)的代碼
//?Interface.js
function?Interface(name,?methods)?
{
????if(arguments.length?!=?2)?{
????????throw?new?Error("接口構(gòu)造函數(shù)含"?+?arguments.length?+?"個(gè)參數(shù),?但需要2個(gè)參數(shù).");
????}
????
????this.name?=?name;
????this.methods?=?[];
????
????if(methods.length?<?1)?{
????????throw?new?Error("第二個(gè)參數(shù)為空數(shù)組.");
????}
????
????for(var?i?=?0,?len?=?methods.length;?i?<?len;?i++)?{
????????
????????if(typeof?methods[i][0]?!==?'string')?{
????????????throw?new?Error("接口構(gòu)造函數(shù)第一個(gè)參數(shù)必須為字符串類型.");
????????}
????????for(var?j?=?1;?j?<?methods[i].length;?j++)?{
????????????if(methods[i][j]?&&?typeof?methods[i][j]?!==?'number')?{
????????????????throw?new?Error("接口構(gòu)造函數(shù)第二個(gè)參數(shù)以上必須為整數(shù)類型.");
????????????}
????????}????????
????????this.methods.push(methods[i]);
????}????
};
Interface.registerImplements?=?function(object)?{
????
????if(arguments.length?<?2)?{
????????throw?new?Error("接口的實(shí)現(xiàn)必須包含至少2個(gè)參數(shù).");
????}
????for(var?i?=?1,?len?=?arguments.length;?i?<?len;?i++)?{
????????var?interface?=?arguments[i];
????????if(interface.constructor?!==?Interface)?{
????????????throw?new?Error("從第2個(gè)以上的參數(shù)必須為接口實(shí)例.");
????????}
????????
????????for(var?j?=?0,?methodsLen?=?interface.methods.length;?j?<?methodsLen;?j++)?{
????????????
????????????var?method1?=?interface.methods[j][0];
????????????var?arr1?=?interface.methods[j].slice(1).sort(compareNumber);
????????????for(var?k?=?0;?k?<?object.methodArr.length;?k++)?{
????????????????var?method2?=?object.methodArr[k][0];
????????????????if(method1?===?method2)
????????????????{
????????????????????var?arr2?=?object.methodArr[k].slice(1).sort();
????????????????????if(ComareArray(arr1,arr2))
????????????????????{
????????????????????????break;
????????????????????}
????????????????????else
????????????????????{
????????????????????????throw?new?Error("接口的實(shí)現(xiàn)對(duì)象不能執(zhí)行"?+?interface.name?+?"的接口方法"?+?method1?+?",因?yàn)樗也坏交蛘卟黄ヅ?");
????????????????????}
????????????????}
????????????????
????????????}
????????}
????}
};
function?compareNumber(num1,?num2)?{
????var?iNum1?=?parseInt(num1);
????var?iNum2?=?parseInt(num2);
????if(iNum1?<?iNum2)?return?-1;
????else?if(iNum1?>?iNum2)?return?1;
????else?return?0;
}
function?ComareArray(arr1,?arr2)?{
????if(arr1.length!=arr2.length)
????{
????????return?false;
????}
????for(var?i?=?0;?i?<?arr1.length;?i++)?{
????????if(arr1[i]!==arr2[i])
????????{
????????????return?false;
????????}
????}
????return?true;
}
Function.prototype.getParameters?=?function()?{
????var?str?=?this.toString();
????var?paramString?=?str.slice(str.indexOf('(')?+?1,?str.indexOf(')')).replace(/\s*/g,'');?????//取得參數(shù)字符串
????
????try
????{
????????return?(paramString.length?==?0???[]?:?paramString.split(','));
????}
????catch(err)
????{
????????throw?new?Error("函數(shù)不合法!");
????}
}
//?demo.js?
function?Overload(method)
{
????this.methods?=?[];
????
????for(var?i?=?1;?i?<=?arguments.length?-?1;?i++)
????{
????????methods.push(arguments[i].length);
????}
????OverloadNumber.methodArr.push([arguments[0]].concat(methods));
????OverloadNumber.argumentArr.push(arguments);
????return?function()
????{
????????for(var?i?=?0;?i?<?OverloadNumber.methodArr.length;?i++)
????????{
????????????if(OverloadNumber.methodArr[i][0]?==?method)
????????????{
????????????????var?index?=?OverloadNumber.methodArr[i].slice(1).indexOf(arguments.length);
????????????????if(index?!=?-1)
????????????????{??
????????????????????return?OverloadNumber.argumentArr[i][index+1].apply(this,arguments);
????????????????}
????????????}
????????}
????????
????????throw?new?Error("參數(shù)不匹配!");
????}
}
var?INumber?=?new?Interface("INumber",?[["Add",1,0,2,3],["Sub",1,2]]);?//其中1,0,2,3之類屬于重載函數(shù)參數(shù)個(gè)數(shù),可以不按先后順序
function?OverloadNumber()?{
????Interface.registerImplements(OverloadNumber,?INumber);
}
OverloadNumber.methodArr?=?[];
OverloadNumber.argumentArr?=?[];
OverloadNumber.prototype?=?{
????
????Add?:?Overload(?????????????????//算術(shù)加
????????????"Add",
????????????function(a,b)?{
????????????????return?a?+?b;
????????????},
????????????function(a)?{
????????????????return?++a;
????????????},
????????????function(a,b,c)?{
????????????????return?a+b+c;
????????????},
????????????function()?{
????????????????return?-1;
????????????}
????????),
????Sub?:?Overload(?????????????????//算術(shù)減
????????????"Sub",
????????????function(a)?{
????????????????return?--a;
????????????},
????????????function(a,b)?{
????????????????return?a?-?b;
????????????}
????????)
}
調(diào)用如下:
調(diào)用方法var?number?=?new?OverloadNumber();
alert("4?-?1?=?"?+?number.Sub(4,1));
alert("++3?=?"?+?number.Add(3));
alert("4?+?6?=?"?+?number.Add(4,?6));
alert("4?+?6?+?5?=?"?+?number.Add(4,?6,?5));
alert(number.Add());
alert("3?-?1?=?"?+?number.Sub(3,1));
alert("--10?=?"?+?number.Sub(10));
alert(number.Add(4,?6,?5,?9));?//?Error!參數(shù)不匹配
)
--------------------
var?IPublisher?=?new?Interface('IPublisher',?[['registerSubscriber',1],?['removeSubscriber',1],?['notifySubscribers']]);所有的依賴者(訂閱者)將要注冊(cè)于它的實(shí)現(xiàn)類。
2. 添加ISubscriber.js,它作為系統(tǒng)的訂閱者“接口”:
3. 現(xiàn)在開始實(shí)現(xiàn)我們的IPublisher的具體類,添加OrderData.js,它作為一個(gè)訂單數(shù)據(jù)類,讓其繼承IPublisher的接口:
????this._subscribers?=?new?Array();??//觀察者列表
????this.ProductName?=?"";??????????//商品名稱
????this.ProductPrice?=?0.0;????????//商品價(jià)格
????this.Recommend?=?0;?????????????//推薦指數(shù)
????this.productCount?=?0;??????????//購(gòu)買個(gè)數(shù)
????Interface.registerImplements(this,?IPublisher);
}
OrderData.prototype?=?{
????registerSubscriber?:?function(subscriber)?{?//注冊(cè)訂閱者
????????this._subscribers.push(subscriber);
????},
????removeSubscriber?:?function(subscriber)?{???//刪除指定訂閱者
????????var?i?=?_subscribers.indexOf(subscriber);
????????if(i?>?0)
????????????_subscribers.slice(i,1);
????},
????notifySubscribers?:?function()?{??//通知各個(gè)訂閱者
????????for(var?i?=?0;?i?<?this._subscribers.length;?i++)
????????{
????????????this._subscribers[i].update(this.ProductName,?this.ProductPrice,?this.Recommend,?this.ProductCount);
????????}
????},
????SubmitOrder?:?function(productName,productPrice,recommend,productCount)?{???//提交訂單
????????this.ProductName?=?productName;
????????this.ProductPrice?=?productPrice;
????????this.Recommend?=?recommend;
????????this.ProductCount?=?productCount;
????????this.notifySubscribers();
????}
}
這里簡(jiǎn)單介紹下,OrderData構(gòu)造函數(shù)中設(shè)置訂閱者列表,以及商品屬性;
Interface.registerImplements(this, IPublisher);? 實(shí)際上是讓OrderData繼承IPublisher接口;
registerSubscriber,removeSubscriber,notifySubscribers實(shí)際上覆蓋了從IPublisher繼承上來(lái)的“接口”方法,這樣保存了這個(gè)類的方法調(diào)用,其中notifySubscribers為通知所有的訂閱者更新信息;
4. 實(shí)現(xiàn)ISubscriber的具體類,添加Subscriber.js,它里面包含三個(gè)訂閱者,1)MsgBox類,短信箱列表;2)ThisOrder類,該條訂單名片;3)OrderList類,我的訂單列表;并且讓其三都繼承ISubscriber的“接口”:
{
????this.Publisher=?publisher;
????this.Publisher.registerSubscriber(this);
????Interface.registerImplements(this,?ISubscriber);
}?
MsgBox.prototype.update?=?function(productName,productPrice,recommend,productCount)?{
//????具體實(shí)現(xiàn)
}?
??
function?ThisOrder(publisher)
{???
????this.Publisher?=?publisher;
????this.Publisher.registerPublisher(this);
????Interface.registerImplements(this,?ISubscriber);
}?
ThisOrder.prototype.update?=?function(productName,productPrice,recommend,productCount)?{?
//????具體實(shí)現(xiàn)
}?
??
function?OrderList(publisher)
{
????this.Publisher =?publisher;
????this.Publisher.registerPublisher(this);
????Interface.registerImplements(this,?ISubscriber);
}?
OrderList.prototype.update?=?function(productName,productPrice,recommend,productCount)?{?
//????具體實(shí)現(xiàn)
}?
看到Subscriber實(shí)現(xiàn)類們的構(gòu)造函數(shù)中的內(nèi)容了么?它把出版者類參數(shù)賦值于Subscriber實(shí)現(xiàn)類們的Publisher對(duì)象,然后在該對(duì)象上注冊(cè)this訂閱者自己,這樣Publisher對(duì)象上就注冊(cè)了Subscriber對(duì)象,并且以Array對(duì)象的方式存儲(chǔ)起來(lái);
5. 好了,IPublisher.js,ISubscriber.js,OrderData.js,Subscriber.js都創(chuàng)建好了,現(xiàn)在需要一個(gè)aspx界面來(lái)使用它們了:
????<table?width="600px"?cellpadding="0"?cellspacing="1"?class="grid">
????????<thead>
????????????<tr>
????????????????<th>
????????????????????商品名
????????????????</th>
????????????????<th>
????????????????????市場(chǎng)價(jià)
????????????????</th>
????????????????<th>
????????????????????推薦指數(shù)
????????????????</th>
????????????????<th>
????????????????????數(shù)量
????????????????</th>
????????????</tr>
????????</thead>
????????<tbody>
????????????<tr>
????????????????<td>
????????????????????<span?id="productName">你必須知道的.NET</span>
????????????????</td>
????????????????<td?align="center">
????????????????????<span?id="productPrice">69.8</span>
????????????????</td>
????????????????<td?align="center">
????????????????????<span?id="recommend">10</span>
????????????????</td>
????????????????<td?align="center">
????????????????????<span?id="productCount">1</span>
????????????????</td>
????????????</tr>
????????</tbody>
????????<tfoot>
????????????<tr>
????????????????<td?align="right"?colspan="4">
????????????????????<input?type="button"?id="btnSubmit"?value="?結(jié)?算?"?/>
????????????????</td>
????????????</tr>
????????</tfoot>
????</table>
</div>
<div?style="width:?1000px;">
????<div?id="MsgBoxContainer">
????????<h2>
????????????您的短信箱</h2>
????????<table?width="100%"?cellspacing="1"?cellpadding="0"?class="grid">
????????????<thead>
????????????????<tr>
????????????????????<th>
????????????????????????內(nèi)容
????????????????????</th>
????????????????????<th?style="width:?100px;">
????????????????????????發(fā)布日期
????????????????????</th>
????????????????</tr>
????????????</thead>
????????????<tbody?id="MsgBoxResult">
????????????</tbody>
????????</table>
????</div>
????<div?id="ThisOrderContainer">
????????<h2>
????????????您剛提交的訂單名片</h2>
????????<div?id="ThisOrderResult">
????????</div>
????</div>
????<div?id="OrderListContainer">
????????<h2>
????????????您已買的商品列表</h2>
????????<table?width="100%"?cellspacing="1"?cellpadding="0"?class="grid">
????????????<thead>
????????????????<tr>
????????????????????<th>
????????????????????????商品名
????????????????????</th>
????????????????????<th>
????????????????????????市場(chǎng)價(jià)
????????????????????</th>
????????????????????<th>
????????????????????????推薦指數(shù)
????????????????????</th>
????????????????????<th>
????????????????????????數(shù)量
????????????????????</th>
????????????????????<th?style="width:?100px;">
????????????????????????發(fā)布日期
????????????????????</th>
????????????????</tr>
????????????</thead>
????????????<tbody?id="OrderListResult">
????????????</tbody>
????????</table>
????</div>
????<div?class="clear">
????</div>
</div>
6. 創(chuàng)建一個(gè)OrderSend.js,編寫相關(guān)的JS代碼了,以下是核心代碼:
$("#btnSubmit").click(function(){????var?productName?=?$("#productName").html();
????var?productPrice?=?parseFloat($("#productPrice").html());
????var?recommend?=?parseInt($("#recommend").html());
????var?productCount?=?parseInt($("#productCount").html());
????var?orderData?=?new?OrderData();????????????//實(shí)例化Publisher的實(shí)現(xiàn)類orderData
????var?msgBox?=?new?MsgBox(orderData);?????????//orderData作為MsgBox構(gòu)造函數(shù)的參數(shù)進(jìn)行傳遞?
????var?thisOrder?=?new?ThisOrder(orderData);???//orderData作為ThisOrder構(gòu)造函數(shù)的參數(shù)進(jìn)行傳遞?
????var?orderList?=?new?OrderList(orderData);???//orderData作為OrderList構(gòu)造函數(shù)的參數(shù)進(jìn)行傳遞?
????orderData.SubmitOrder(productName,productPrice,recommend,productCount);?????//提交相關(guān)商品信息
});
通過(guò)點(diǎn)擊頁(yè)面上的“提交”,將三個(gè)Subscriber實(shí)現(xiàn)類注冊(cè)到OrderData(Publisher實(shí)現(xiàn)類)中去,這樣只要OrderData對(duì)象提交新商品信息上去,也就是狀態(tài)更新,那么三個(gè)Subscriber實(shí)現(xiàn)類就會(huì)被通知而更新自身相關(guān)的內(nèi)容了。
頁(yè)面實(shí)現(xiàn)效果如下:
點(diǎn)擊“結(jié)算”按鈕,如下:
?
這里只是簡(jiǎn)單的對(duì)于三個(gè)Subscriber進(jìn)行更新,關(guān)于update方法中的實(shí)現(xiàn),這里不在貼出來(lái)了,具體可以下載源代碼查看看;在update方法中可以編寫你想要的操作以及顯示結(jié)果,如利用$.ajax進(jìn)行數(shù)據(jù)操作和數(shù)據(jù)展示,這里就留著大家自己發(fā)揮吧:)
總結(jié)
該篇文章用Javascript來(lái)設(shè)計(jì)觀察者模式的思路,通過(guò)觸發(fā)變化通知的方式來(lái)請(qǐng)求狀態(tài)更新,利用一個(gè)簡(jiǎn)單的購(gòu)書網(wǎng)站來(lái)實(shí)踐。
本篇到此為止,謝謝大家閱讀!
?
附:相關(guān)源代碼下載
?
參考文獻(xiàn):《Head First Design Pattern》
本系列文章轉(zhuǎn)載時(shí)請(qǐng)注明出處,謝謝合作!
?相關(guān)系列文章:
Javascript亂彈設(shè)計(jì)模式系列(6) - 單件模式(Singleton)
Javascript亂彈設(shè)計(jì)模式系列(5) - 命令模式(Command)
Javascript亂彈設(shè)計(jì)模式系列(4) - 組合模式(Composite)
Javascript亂彈設(shè)計(jì)模式系列(3) - 裝飾者模式(Decorator)
Javascript亂彈設(shè)計(jì)模式系列(2) - 抽象工廠以及工廠方法模式(Factory)
Javascript亂彈設(shè)計(jì)模式系列(1) - 觀察者模式(Observer)
Javascript亂彈設(shè)計(jì)模式系列(0) - 面向?qū)ο蠡A(chǔ)以及接口和繼承類的實(shí)現(xiàn)
轉(zhuǎn)載于:https://www.cnblogs.com/liping13599168/archive/2009/01/06/1366599.html
總結(jié)
以上是生活随笔為你收集整理的Javascript乱弹设计模式系列(1) - 观察者模式(Observer)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Hive初识(二)
- 下一篇: C#种将String类型转换成int型