JS設計模式——享元模式

什麼是享元模式?

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章