用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到底有什麼差別,做了一下小小的測試:

  1. 產生五千個manual comments跟五千個trackback comments
  2. 盡量讓兩個方法不會差太多.....(這比較難精確)
  3. 動態產生完html以後,中途使用的變數用delete刪除(後來發現沒有用)
  4. 在show前後使用Date.getTime()來粗略估計動作執行時間
  5. 用windows的工作管理員粗略估計使用的記憶體
  6. 使用firefox的安全模式,首頁設為空白,然後再開啟檔案,分別紀錄打開firefox後、檔案載入後、動作執行後的記憶體使用量

.....結果大致符合預期:使用flyweight速度稍慢,沒有使用flyweight記憶體用比較多。記憶體的差距大約有3932K,速度差距大約有584.8ms。