每天一個設計模式之享元模式

作者按:《每天一個設計模式》旨在初步領會設計模式的精髓,目前採用javascriptpython兩種語言實現。誠然,每種設計模式都有多種實現方式,但此小冊只記錄最直截了當的實現方式 :)

個人技術博客-godbmw.com 歡迎來玩! 每週至少 1 篇原創技術分享,還有開源教程(webpack、設計模式)、面試刷題(偏前端)、知識整理(每週零碎),歡迎長期關注!本篇博客地址是:《每天一個設計模式之享元模式》

如果您也想進行知識整理 + 搭建功能完善/設計簡約/快速啓動的個人博客,請直接戳theme-bmw

0. 項目地址

1. 什麼是“享元模式”?

享元模式:運用共享技術來減少創建對象的數量,從而減少內存佔用、提高性能。
  1. 享元模式提醒我們將一個對象的屬性劃分爲內部和外部狀態

    • 內部狀態:可以被對象集合共享,通常不會改變
    • 外部狀態:根據應用場景經常改變
  2. 享元模式是利用時間換取空間的優化模式。

2. 應用場景

享元模式雖然名字聽起來比較高深,但是實際使用非常容易:只要是需要大量創建重複的類的代碼塊,均可以使用享元模式抽離內部/外部狀態,減少重複類的創建。

爲了顯示它的強大,下面的代碼是簡單地實現了大家耳熟能詳的“對象池”,以彰顯這種設計模式的魅力。

3. 代碼實現

這裏利用pythonjavascript實現了一個“通用對象池”類--ObjectPool。這個類管理一個裝載空閒對象的數組,如果外部需要一個對象,直接從對象池中獲取,而不是通過new操作

對象池可以大量減少重複創建相同的對象,從而節省了系統內存,提高運行效率。

爲了形象說明“享元模式”在“對象池”實現和應用,特別準備了模擬了File類,並且模擬了“文件下載”操作。

通過閱讀下方代碼可以發現:對於File類,內部狀態是pool屬性和download方法;外部狀態是namesrc(文件名和文件鏈接)。藉助對象池,實現了File類的複用。

注:爲了方便演示,Javascript實現的是併發操作,Python實現的是串行操作。輸出結果略有不同。

3.1 Python3 實現

from time import sleep


class ObjectPool:  # 通用對象池
    def __init__(self):
        self.__pool = []

    # 創建對象
    def create(self, Obj):
        # 對象池中沒有空閒對象,則創建一個新的對象
        # 對象池中有空閒對象,直接取出,無需再次創建
        return self.__pool.pop() if len(self.__pool) > 0 else Obj(self)

    # 對象回收
    def recover(self, obj):
        return self.__pool.append(obj)

    # 對象池大小
    def size(self):
        return len(self.__pool)


class File:  # 模擬文件對象
    def __init__(self, pool):
        self.__pool = pool

    def download(self):  # 模擬下載操作
        print('+ 從', self.src, '開始下載', self.name)
        sleep(0.1)
        print('-', self.name, '下載完成')
        # 下載完畢後,將對象重新放入對象池
        self.__pool.recover(self)


if __name__ == '__main__':
    obj_pool = ObjectPool()

    file1 = obj_pool.create(File)
    file1.name = '文件1'
    file1.src = 'https://download1.com'
    file1.download()

    file2 = obj_pool.create(File)
    file2.name = '文件2'
    file2.src = 'https://download2.com'
    file2.download()

    file3 = obj_pool.create(File)
    file3.name = '文件3'
    file3.src = 'https://download3.com'
    file3.download()

    print('*' * 20)
    print('下載了3個文件, 但其實只創建了', obj_pool.size(), '個對象')

輸出結果(這裏爲了方便演示直接使用了sleep方法,沒有再用多線程模擬):

+ 從 https://download1.com 開始下載 文件1
- 文件1 下載完成
+ 從 https://download2.com 開始下載 文件2
- 文件2 下載完成
+ 從 https://download3.com 開始下載 文件3
- 文件3 下載完成
********************
下載了3個文件, 但其實只創建了 1 個對象

3.2 ES6 實現

// 對象池
class ObjectPool {
  constructor() {
    this._pool = []; //
  }

  // 創建對象
  create(Obj) {
    return this._pool.length === 0
      ? new Obj(this) // 對象池中沒有空閒對象,則創建一個新的對象
      : this._pool.shift(); // 對象池中有空閒對象,直接取出,無需再次創建
  }

  // 對象回收
  recover(obj) {
    return this._pool.push(obj);
  }

  // 對象池大小
  size() {
    return this._pool.length;
  }
}

// 模擬文件對象
class File {
  constructor(pool) {
    this.pool = pool;
  }

  // 模擬下載操作
  download() {
    console.log(`+ 從 ${this.src} 開始下載 ${this.name}`);
    setTimeout(() => {
      console.log(`- ${this.name} 下載完畢`); // 下載完畢後, 將對象重新放入對象池
      this.pool.recover(this);
    }, 100);
  }
}

/****************** 以下是測試函數 **********************/

let objPool = new ObjectPool();

let file1 = objPool.create(File);
file1.name = "文件1";
file1.src = "https://download1.com";
file1.download();

let file2 = objPool.create(File);
file2.name = "文件2";
file2.src = "https://download2.com";
file2.download();

setTimeout(() => {
  let file3 = objPool.create(File);
  file3.name = "文件3";
  file3.src = "https://download3.com";
  file3.download();
}, 200);

setTimeout(
  () =>
    console.log(
      `${"*".repeat(50)}\n下載了3個文件,但其實只創建了${objPool.size()}個對象`
    ),
  1000
);

輸出結果如下:

+ 從 https://download1.com 開始下載 文件1
+ 從 https://download2.com 開始下載 文件2
- 文件1 下載完畢
- 文件2 下載完畢
+ 從 https://download3.com 開始下載 文件3
- 文件3 下載完畢
**************************************************
下載了3個文件,但其實只創建了2個對象

4. 參考

  • 《JavaScript 設計模式和開發實踐》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章