介紹
享元(flyweight)模式是一種用于性能優(yōu)化的模式,享元模式的核心是運用共享技術來有效支持大量細粒度的對象。
享元模式可以避免大量非常相似類的開銷,在程序設計中,有時需要生產大量細粒度的類實例來表示數(shù)據(jù),如果能發(fā)現(xiàn)這些實例除了幾個參數(shù)以外,開銷基本相同的話,就可以大幅度較少需要實例化的類的數(shù)量。如果能把那些參數(shù)移動到類實例的外面,在方法調用的時候將他們傳遞進來,就可以通過共享大幅度第減少單個實例的數(shù)目。
那么如果在JavaScript中應用享元模式呢?有兩種方式:
- 第一種是應用在數(shù)據(jù)層上,主要是應用在內存里大量相似的對象上;
- 第二種是應用在DOM層上,享元可以用在中央事件管理器上用來避免給父容器里的每個子元素都附加事件句柄
享元與數(shù)據(jù)層
享元模式要求將對象的屬性劃分為內部狀態(tài)與外部狀態(tài)(狀態(tài)在這里通常指屬性)。享元模式的目標是盡量減少共享對象的數(shù)量,關于如何劃分內部狀態(tài)和外部狀態(tài),下面的幾條經驗提供了一些指引。
- 內部狀態(tài)存儲于對象內部。
- 內部狀態(tài)可以被一些對象共享。
- 內部狀態(tài)獨立于具體的場景,通常不會改變。
- 外部狀態(tài)取決于具體的場景,并根據(jù)場景而變化,外部狀態(tài)不能被共享
說白點,就是先捏一個的原始模型,然后隨著不同場合和環(huán)境,再產生各具特征的具體模型,很顯然,在這里需要產生不同的新對象,所以Flyweight模式中常出現(xiàn)Factory模式,F(xiàn)lyweight的內部狀態(tài)是用來共享的,F(xiàn)lyweight factory負責維護一個Flyweight pool(模式池)來存放內部狀態(tài)的對象。
使用實例
如下場景:
圖書館有很多書,設計一個管理系統(tǒng)管理所有的借書信息
假定每個書的內容有:title、author、id、site
每本書被借出時間、借書人、歸還日期、是否可用:
checkoutDate
checkoutMember
dueReturnDate
availability
我們可以將數(shù)據(jù)分成內部和外部兩種數(shù)據(jù),和book對象相關的數(shù)據(jù)(title, author 等)可以歸結為內部屬性,而(checkoutMember, dueReturnDate等)可以歸結為外部屬性。這樣,如下代碼就可以在同一本書里共享同一個對象了,因為不管誰借的書,只要書是同一本書,基本信息是一樣的:
// 內部公共屬性
var Book = function(title, author, id, ISBN) {
this.title = title;
this.author = author;
this.id = id;
this.ISBN = ISBN
}
定義基本工廠
讓我們來定義一個基本工廠,用來檢查之前是否創(chuàng)建該book的對象,如果有就返回,沒有就重新創(chuàng)建并存儲以便后面可以繼續(xù)訪問,這確保我們?yōu)槊恳环N書只創(chuàng)建一個對象:
// book工廠
var BookFactory = (function(){
var existingBooks = {};
return {
createBook: function(title, author, id, ISBN) {
// 查找之前是否創(chuàng)建
var existingBook = existingBooks[ISBN];
if (existingBook) {
return existingBook;
} else {
// 如果沒有,就創(chuàng)建一個,然后保存
var book = new Book(title, author, id, ISBN);
existingBooks[ISBN] = book;
return book;
}
}
}
})();
管理外部狀態(tài)
外部狀態(tài),相對就簡單了,除了我們封裝好的book,其它都需要在這里管理:
var BookRecord = (function() {
var bookRecordDatabase = {};
return {
addBook: function(title, author, id, ISBN, checkoutDate, checkoutMember, dueReturnDate, availability) {
var book = BookFactory.createBook(title, author, id, ISBN);
bookRecordDatabase[id] = {
checkoutMember: checkoutMember,
checkoutDate: checkoutDate,
dueReturnDate: dueReturnDate,
availability: availability,
book: book
};
console.log(bookRecordDatabase);
},
updateCheckoutStatus: function(bookID, newStatus, checkoutDate, checkoutMember, newReturnDate){
var record = bookRecordDatabase[bookID];
record.availability = newStatus;
record.checkoutDate = checkoutDate;
record.checkoutMember = checkoutMember;
record.dueReturnDate = newReturnDate;
},
extendCheckoutPeriod: function(bookID, newReturnDate) {
bookRecordDatabase[bookID].dueReturnDate = newReturnDate;
},
isPastDue: function(bookID) {
var currentDate = new Date();
return currentDate.getTime() > Date.parse(bookRecordDatabase[bookID].dueReturnDate);
}
}
})();
通過這種方式,我們做到了將同一種圖書的相同信息保存在一個bookmanager對象里,而且只保存一份;相比之前的代碼,就可以發(fā)現(xiàn)節(jié)約了很多內存。
總結
享元模式是為解決性能問題而生的模式,這跟大部分模式的誕生原因都不一樣。在一個存在
大量相似對象的系統(tǒng)中,享元模式可以很好地解決大量對象帶來的性能問題。
參考引用資料
《JavaScript設計模式與開發(fā)實踐》