用javascript試作flyweight pattern
Thu, 28 Jun 2007 21:54:38 +0800同樣是在ajaxpatterns.org網站上看到的,他是假設一篇blog文章有200個manual comments與50個trackback comments,每個comments使用一個物件實體來掌控的狀況下,有可能會耗費比較大量的記憶體。與其用250個物件來掌控這些comments,不如參考flyweight pattern的方法,用兩個物件來掌控。
因為只要符合flyweight的結構就可以,所以稍微簡化ajaxpatterns.org所講的狀況,假設manual comments只顯示comment的文字,trackback則會顯示trackback的網址。如果沒有應用flyweight pattern,程式可能會像這樣:
function manual (content) { this.content = content; this.draw = function (target) { var a = document.createElement("div"); a.innerHTML = this.content; ......... target.appendChild(a); a = null; delete a; } } var manuals = []; for (key in comments) { manuals.push(new manual(comments[key])); } for (key in manuals) { manuals[key].draw(target); }
(假設comments資料放在comments陣列變數裡面,最後會顯示在target變數參考的物件內)然後用類似的方法處理trackback。
function trackback (content) { this.content = content; this.draw = function (target) { var a = document.createElement("div"); a.innerHTML = "<a href='"+this.content+"' target='_blank'>"+this.content+"</a>"; ......... target.appendChild(a); a = null; delete a; } } var trackbacks= []; for (key in comments) { trackbacks.push(new trackback(comments[key])); } for (key in trackbacks) { trackbacks[key].draw(target); }
從以上的程式可以看出來,總共產生了非常多的manual物件實例還有trackback物件實例來顯示所有的comments,會佔用比較多記憶體。
改成用flyweight pattern來實作的話,就要稍微調整一下。首先設計一個commentManager函數物件,用pool陣列來存放相應的comment物件,用draw方法來顯示comments,用_commentFactory方法來從pool中取得comment物件參考。如果物件不在pool陣列中,則加入。這樣可以保證每一種物件只使用一個實例。以下是flyweight.js的內容:
function commentManager () { this.pool = {}; this._commentFactory = function (type) { if (this.pool[type]) { return this.pool[type]; } else { if (eval(type)) { this.pool[type] = eval("new "+type+"()"); return this.pool[type]; } else { return new Flyweight(); } } } this.draw = function (target, comments) { for (obj in comments) { this._commentFactory(comments[obj][0]).draw(target, comments[obj][1]); } } } function Flyweight () { this.draw = function (target, content) { } } var comments = [];
接下來設計把外部狀態移出後的manual物件,以及所有manual comments的資料,放在manual.js裡面:
function manual () { this.draw = function (target, content) { var a = document.createElement("div"); a.style.border = "dashed gray 1px"; a.style.backgroundColor = "#DDDDDD"; a.style.margin = "5px 5px 5px 5px"; a.style.padding = "5px 5px 5px 5px"; a.innerHTML = content; target.appendChild(a); a = null; delete a; } } manual.prototype = new Flyweight; comments.push( ["manual", "這是第1篇comment,其他還有很多comments。"], ["manual", "這是第2篇comment,其他還有很多comments。"], .......... ["manual", "這是第199篇comment,其他還有很多comments。"], ["manual", "這是第200篇comment,其他還有很多comments。"]);
用類似的方式設計trackback.js:
function trackback () { this.draw = function (target, content) { var a = document.createElement("div"); a.style.border = "dashed #334455 1px"; a.style.backgroundColor = "#99CC99"; a.style.margin = "5px 5px 5px 5px"; a.style.padding = "5px 5px 5px 5px"; a.innerHTML = "<a href='"+content+"' target='_blank'>"+content+"</a>"; target.appendChild(a); a = null; delete a; } } trackback.prototype = new Flyweight; comments.push ( ["trackback", "http://www.test.com/trackback?id=0"], ["trackback", "http://www.test.com/trackback?id=1"], .................. ["trackback", "http://www.test.com/trackback?id=48"], ["trackback", "http://www.test.com/trackback?id=49"]);
稍微說明一下,這裡面用到一些耦合來讓不同種的comment可以動態,模組化地載入。在commentManager._commentFactory函數中,傳入的type變數與manual.js中的manual函數與comments陣列中的"manual"是有關係的,因為名稱相同,所以可以用eval來將manual函數物件的實例放進pool中。因為有這樣的耦合,還有需要用comments變數存放comments資料,所以flyweight.js必須在manual.js以及trackback.js之前載入。
接下來做一個網頁來測試一下效果:
<html> <script src="flyweight.js"></script> <body> <div style="border: solid 1px black; vertical-align: top">Comments <input type="button" value="產生" onclick="show()"> <div id="comment"> </div> </div> <script src="manual.js"></script> <script src="trackback.js"></script> <script> function show() { var tmp = document.getElementById("comment"); var commentManager1 = new commentManager(); commentManager1.draw(tmp, comments); } </script> </body> <html>
(上面的「<」是全形的喔,直接拷貝不能用,請注意)
接下來看一下執行的效果,第一張是還未顯示comments前:
第二張是顯示comments:
第三章可以看到有200個manual comments,接下來是50個trackback comments:
有興趣試試的話,以下是試作的網址:
http://www.fillano.idv.tw/test82.html
[補充說明] 2007-6-29
為了比較使用flyweight跟不使用flyweight到底有什麼差別,做了一下小小的測試:
- 產生五千個manual comments跟五千個trackback comments
- 盡量讓兩個方法不會差太多.....(這比較難精確)
- 動態產生完html以後,中途使用的變數用delete刪除(後來發現沒有用)
- 在show前後使用Date.getTime()來粗略估計動作執行時間
- 用windows的工作管理員粗略估計使用的記憶體
- 使用firefox的安全模式,首頁設為空白,然後再開啟檔案,分別紀錄打開firefox後、檔案載入後、動作執行後的記憶體使用量
.....結果大致符合預期:使用flyweight速度稍慢,沒有使用flyweight記憶體用比較多。記憶體的差距大約有3932K,速度差距大約有584.8ms。