一。享元模式的結構
0.內部狀態與外部狀態
在享元對象內部並且不會隨著環境改變而改變的共享部分,可以稱之為享元對象的內部狀態,反之隨著環境改變而改變的,不可共享的狀態稱之為外部狀態。
簡單地說,內部狀態是對象本身的屬性,外部狀態是管理這些對象所需的額外的屬性,相同的對象內部狀態相同,但外部狀態可能不同(比如 2 本相同的書被 2 個人借走了)
1.享元
享元是相似對象(書的例子中指的是完全相同的書,而不是題材相似的書)間可共享的屬性的集合,比如書的名字、作者、ISBN 等等,完全相同的書這些信息都是相同的,沒有必要把相同的屬性在多個相似對象中保存多份,享元負責把這些屬性分離出來,以便共享
如果可共享的屬性比較複雜,還可以增加抽象享元,以及與之對應的具體享元,還可以有複合享元
2.享元工廠
享元工廠負責創建並管理享元,實現共享邏輯(創建時判斷是否存在,已存在就返回現有對象,否則創建一個)
3.客戶端(Client)
Client 負責調用享元工廠,並存儲管理相似對象所需的額外屬性(比如書的 id,借/還日期,是否在館等等)
二。享元模式實例
// 图书管理
// 书的属性
// id
// title
// author
// genre
// page count
// publisher id
// isbn
// 管理所需的额外属性
// checkout date
// checkout member
// due return date
// availability
// 享元(存储内部状态)
function Book(title, author, genre, pageCount, publisherId, isbn) {
this.title = title;
this.author = author;
this.genre = genre;
this.pageCount = pageCount;
this.publisherId = publisherId;
this.isbn = isbn;
}
// 享元工厂(创建/管理享元)
var BookFactory = (function() {
var existingBooks = {};
var existingBook = null;
return {
createBook: function(title, author, genre, pageCount, publisherId, isbn) {
// 如果书籍已经创建,,则找到并返回
// !!强制返回 bool 类型
existingBook = existingBooks[isbn];
if (!!existingBook) {
return existingBook;
}
else {
// 如果不存在选择创建该书的新实例并保存
var book = new Book(title, author, genre, pageCount, publisherId, isbn);
////
console.log('new book');
existingBooks[isbn] = book;
return book;
}
}
}
})();
// 客户端(存储外部状态)
var BookRecordManager = (function() {
var bookRecordDatabase = {};
return {
// 添加新书到数据库
addBookRecord: function(id, title, author, genre, pageCount, publisherId, isbn,
checkoutDate, checkoutMember, dueReturnDate, availability) {
var book = BookFactory.createBook(title, author, genre, pageCount, publisherId, isbn);
bookRecordDatabase[id] = {
checkoutMember: checkoutMember,
checkoutDate: checkoutDate,
dueReturnDate: dueReturnDate,
availability: availability,
book: book
}
},
updateCheckStatus: 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 currDate = new Date();
return currDate.getTime() > Date.parse(bookRecordDatabase[bookId].dueReturnDate);
}
};
})();
// test
// isbn 号是书籍的唯一标识,以下三条只会创建一个 book 对象
BookRecordManager.addBookRecord(1, 'x', 'x', 'xx', 300, 10001, '100-232-32'); // new book
BookRecordManager.addBookRecord(1, 'xx', 'xx', 'xx', 300, 10001, '100-232-32');
BookRecordManager.addBookRecord(1, 'xxx', 'xxx', 'xxx', 300, 10001, '100-232-32');
如果需要管理的書籍數量非常大,那麼使用享元模式節省的記憶體將是一個可觀的數目
三.jQuery 與享元模式
例子是 JAMES PADOLSEY 的 jQuery.single,如下:
jQuery.single = (function(o){
var collection = jQuery([1]); // Fill with 1 item, to make sure length === 1
return function(element) {
// Give collection the element:
collection[0] = element;
// Return the collection:
return collection;
};
}());
// window.$_ = jQuery.single; // 定义别名
// test
jQuery('a').click(function(){
var html = jQuery.single(this).next().html(); // Method chaining works!
alert(html);
// etc. etc.
});
維護了一個單例 collection,避免多次用 $() 包裹同一個 DOM 對象帶來的記憶體消耗(會創建多個 jQuery 對象),使用 jQuery.single 永遠都只會創建一個 jQuery 對象,節省了創建額外 jQuery 對象消耗的時間,還減少了記憶體開銷,但這樣做最大的問題可能是:jQuery.single 返回的對象無法被緩存。因為內部是單例實現,緩存的對象在下一次調用 jQuery.single 後可能會被改變,所以無法像 $() 一樣隨時緩存。但 據說 直接使用 jQuery.single 獲取單例要比緩存普通 jQuery 對象更快,但為了避免混亂,建議只在需要把 DOM 對象包裹成 jQuery 對象時才使用 jQuery.single 方法
四。享元模式的優缺點
優點
-
減少記憶體開銷
-
提供了一種方便的管理大量相似對象的方法
缺點
-
享元模式需要分離內部狀態和外部狀態,會使邏輯變得更加複雜
-
外部狀態被分離出去後,訪問會產生輕微的額外時耗(時間換空間?)
P.S.如果項目中需要創建大量實例對象,就應該考慮一下享元模式是否適用
參考資料
-
《JavaScript 設計模式》
-
享元模式(Flyweight):提供了很不錯的例子
暫無評論,快來發表你的看法吧