什么是享元模式?
享元模式(Flyweight Pattern)是一种结构型设计模式。从字面意思理解就是共享对象的模式。如果系统中出现大量相似度高,状态变化小的对象,可以尝试重用现有的同类对象来减少内存使用,达到性能优化。
在享元模式中可以共享的相同内容称为内部状态 (Intrinsic State),而那些需要外部环境来设置的不能共享的内容称为外部状态 (Extrinsic State),由于区分了内部状态和外部状态,因此可以通过设置不同的外部状态使得相同的对象可以具有一些不同的特征,而相同的内部状态是可以共享的。
在实际使用中,能够共享的内部状态是有限的,因此享元对象一般都设计为较小的对象,它所包含的内部状态较少,这种对象也称为细粒度对象。享元模式的目的就是使用共享技术来实现大量细粒度对象的复用。
实例
拿图书馆的图书管理来举例子,我们直观感受下在使用享元模式前后整个设计模式的变化。
未使用享元模式
首先,创建一个图书类,每本书都可以通过这个类实例化。
看上去,每本书当成一个对象来维护,没有什么大问题。但是对于图书馆来说,同样的书肯定不止一本,也肯定会被反复借阅,长期下去,内存就会被一个个书籍对象淹没。接下来我们就用享元模式进行优化。
提示:ISBN又称国际标准书号,它是英文International Standard Book Number的缩写
class Book {
constructor(id, name, author, pageCount, isbn, checkoutDate='', checkoutMember='', dueReturnDate='') {
this.id = id
this.name = name
this.author = author
this.pageCount = pageCount
this.isbn = isbn
this.checkoutDate = checkoutDate
this.checkoutMember = checkoutMember
this.dueReturnDate = dueReturnDate
}
getName() {
console.log(this.name)
}
getAuthor() {
console.log(this.author)
}
updateCheckoutStatus(bookId, checkoutDate, checkoutMember, newReturnDate) {
this.id = bookId
this.checkoutDate = checkoutDate
this.checkoutMember = checkoutMember
this.dueReturnDate = newReturnDate
}
isPastDue() {
return Date.now() > this.dueReturnDate
}
}
// 实例化一本 js 书籍
const jsBook = new Book(8,'JS设计模式','Addy',241)
jsBook.getName() // => 'js设计模式'
jsBook.getAuthor() // => 'Addy'
// 这本书被借出,需要更新借出状态
jsBook.updateCheckoutStatus(8, Date.now(), 'zkk', Date.now() + 86400000)
jsBook.isPastDue() // => false
使用享元模式优化
根据享元模式的设计思想,我们可以把书籍数据分为内部状态和外部状态。
- 内部状态:name、author、pageCount,isbn
- 外部状态:id、checkoutDate、checkoutMember、dueReturnDate
我们将类中的外部状态移除,只留下内部状态供指定书名的书籍副本之间共享。
class Book {
constructor(name, author, pageCount, isbn) {
this.name = name
this.author = author
this.pageCount = pageCount
this.isbn = isbn
}
getName() {
console.log(this.name)
}
getAuthor() {
console.log(this.author)
}
}
将对象的数据分开后,我们就可以使用工厂进行书籍的实例化。
class BookFactory {
static existBooks = []
static createBook(name, author, pageCount, isbn) {
if(this.existBooks[isbn]) {
console.log('exist')
return this.existBooks[isbn]
} else {
console.log('new')
const book = new Book(...arguments)
this.existBooks[isbn] = book
return book
}
}
}
下面看下测试结果:
在第二次需要同类 isbn 书籍的时候,并没有重新创建,而是共享了对象池里的已有的对象。
接下来,我们要对分离出来的外部状态进行管理。
class bookRecordManager {
static recordList = []
static addRecord (id, name, author, pageCount, isbn, checkoutDate, checkoutMember, dueReturnDate) {
const book = BookFactory.createBook(name, author, pageCount, isbn)
this.recordList[id] = {
checkoutDate,
checkoutMember,
dueReturnDate,
book
}
}
static updateCheckoutStatus(bookId, checkoutDate, checkoutMember, newReturnDate) {
this.recordList[bookId].checkoutDate = checkoutDate
this.recordList[bookId].checkoutMember = checkoutMember
this.recordList[bookId].dueReturnDate = newReturnDate
}
static isPastDue(bookId) {
return Date.now() > this.recordList[bookId].dueReturnDate
}
}
// 测试
bookRecordManager.addRecord(1, 'js设计模式', 'Addy', 241, 123456, Date.now(), 'zkk', Date.now() + 86400000)
bookRecordManager.addRecord(2, 'js设计模式', 'Addy', 241, 123456, Date.now(), 'yy', Date.now() + 86400000)
bookRecordManager.recordList[1].book.getName()
bookRecordManager.recordList[2].book.getName()
bookRecordManager.isPastDue(1)
打印结果:
至此,我们的优化就做完了,虽然增加了一点复杂性,但是却从设计上解决了性能上的问题。如果有30本相同的书,我们现在只需要存储它一次。