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