一.享元模式的结构
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):提供了很不错的例子