什麼是享元模式?
享元模式(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本相同的書,我們現在只需要存儲它一次。