js常用設計模式7-享元模式 1,假髮工廠 2,享元模式優化 3,內部狀態與外部狀態 4,普通版本上傳文件的例子 5,享元模式優化文件上傳

享元模式是一種用於性能優化的模式,核心是運用共享技術來有效支持大量細粒度的對象。
如果系統中創建了大量類似的對象,導致內存佔用過高,這時候享元模式就很有用了。

1,假髮工廠

假設有一個假髮工廠,生產50種男士假髮和50種女士假髮,這時候我們需要50個男模和50個女模,分別套上假髮,然後拍照:

var Model = function (sex, underware) {
  this.sex = sex
  this.underware = underware
}

Model.prototype.takePhoto = function () {
  console.log('sex:' + this.sex + 'underware:' + this.underware)
}

for (var i = 0; i < 50; i++) {
  var maleModel = new Model('男', 'underware' + i)
  maleModel.takePhoto()
}
for (var i = 0; i < 50; i++) {
  var femaleModel = new Model('女', 'underware' + i)
  femaleModel.takePhoto()
}

很明顯,這種做法略顯沙雕。

2,享元模式優化

實際上,我們只需要兩個模特就夠了,假髮換着套就行。

//效果一樣,感覺好多了
var Model = function (sex) {
  this.sex = sex
}
Model.prototype.takePhoto = function () {
  console.log('sex:' + this.sex + 'underware:' + this.underware)
}

var maleModel = new Model('男')
var femaleModel = new Model('女')

for (var i = 0; i < 50; i++) {
  maleModel.underware = 'underware' + i
  maleModel.takePhoto()
}

for (var i = 0; i < 50; i++) {
  femaleModel.underware = 'underware' + i
  femaleModel.takePhoto()
}

3,內部狀態與外部狀態

享元模式的目標是儘量減少共享對象的數量:
(1)內部狀態存儲於對象內部
(2)內部狀態可以被一些對象共享
(3)內部狀態獨立於具體的場景
(4)外部狀態取決於具體的場景,並根據場景而變化,外部狀態不能被共享
享元模式是一種時間換空間的優化模式

4,普通版本上傳文件的例子

現在我們要實現一個功能:往服務器上傳文件。假設目前只有兩種上傳方式:插件上傳和flash上傳。這倆實現是一樣的,選擇文件之後,都會調用window下的startUpload方法,用戶選擇的文件列表被組合成一個數組files放到該函數的參數列表中。
代碼如下:

  var id = 0;
  //uploadType區分是插件上傳還是flash
    window.startUpload = function (uploadType, files) {
      for (var i = 0, file; file = files[i++];) {
        var upload = new Upload(uploadType, file.fileName, file.fileSize)
        upload.init(id++)
      }
    }

用戶選擇完文件後,startUpload函數會遍歷files數組,挨個創建對應的upload對象。現在我們要定義Upload構造函數,除了初始化init函數之外,我們還需要一個刪除函數delFile。代碼如下:

// uploadType:上傳類型, fileName:文件名, fileSize:文件大小
var Upload = function (uploadType, fileName, fileSize) {
      this.uploadType = uploadType
      this.fileName = fileName
      this.fileSize = fileSize
      this.dom = null
    }
Upload.prototype.init = function (id) {
  var that = this
  this.id = id
  this.dom = document.createElement('div')
  this.dom.innerHTML = `<span>文件名稱:${this.fileName},文件大小:${this.fileSize}</span><button class="delFile">刪除</button>`
  this.dom.querySelector('.delFile').onclick = function () {
    that.delFile()
  }
  document.body.appendChild(this.dom)
}
Upload.prototype.delFile = function () {
  if (this.fileSize < 3000) {
    return this.dom.parentNode.removeChild(this.dom)
  }
  if (window.confirm('確定刪除該文件嗎?' + this.fileName)) {
    return this.dom.parentNode.removeChild(this.dom)
  }
}

現在,讓我們來try一try:

startUpload('plugin', [
  {
    fileName: '1.txt',
    fileSize: 1000
  },
  {
    fileName: '2.txt',
    fileSize: 3000
  },
  {
    fileName: '3.txt',
    fileSize: 5000
  }
])

startUpload('flash', [
  {
    fileName: '4.txt',
    fileSize: 1000
  },
  {
    fileName: '5.txt',
    fileSize: 3000
  },
  {
    fileName: '6.txt',
    fileSize: 5000
  }
])

5,享元模式優化文件上傳

上面這個上傳功能,有多少個需要上傳的對象,就創建了多少個upload對象,非常的浪費。下面,我們用享元模式來重構它。
(1)首先,要明確內部狀態
upload對象依賴於upLoadType屬性,由於我們現在是簡化版的上傳,因此只要明確了upLoadType,這個upload對象是可以被其他文件共享的。而fileName、fileSize都不一樣,因此這倆是外部狀態。
(2)剝離外部狀態
現在,我們的Upload構造函數中,只需要upLoadType這一個參數就可以:

 var Upload = function (uploadType) {
      this.uploadType = uploadType
    }

Upload.prototype.init函數也不需要了,現在upload對象的初始化被放在了uploadManager.add這個函數中。
接下來我們只需要定義Upload.prototype.delFile函數就ok:

  Upload.prototype.delFile = function (id) {
      // 刪除之前,讀取文件實際大小
      uploadManager.setExternalState(id, this)

      if (this.fileSize < 3000) {
        return this.dom.parentNode.removeChild(this.dom)
      }
      if (window.confirm('確定刪除該文件嗎?' + this.fileName)) {
        return this.dom.parentNode.removeChild(this.dom)
      }
    }

(3)工廠函數進行對象實例化
定義一個工廠,用來創建upload對象,如果某種內部狀態(upLoadType)的對象已經被創建過,那麼直接返回這個對象,否則創建新的對象:

   var UploadFactory = (function () {
      var createdFlyWeightObjs = {}

      return {
        create: function (uploadType) {
          if (createdFlyWeightObjs[uploadType]) {
            return createdFlyWeightObjs[uploadType]
          }
          return createdFlyWeightObjs[uploadType] = new Upload(uploadType)
        }
      }
    })()

(4)管理器封裝外部狀態
在(2)中我們提到了uploadManager對象,這個對象負責向UploadFactory提交創建對象的請求,並用一個uploadDatabase對象保存所有的upload對象的外部狀態,以便在程序運行中給upload共享對象設置外部狀態:

  var uploadManager = (function () {
      var uploadDatabase = {}

      return {
        add: function (id, uploadType, fileName, fileSize) {
          var flyWeightObj = UploadFactory.create(uploadType)

          var dom = document.createElement('div')
          dom.innerHTML = `<span>文件名稱:${fileName},文件大小:${fileSize}</span><button class="delFile">刪除</button>`
          dom.querySelector('.delFile').onclick = function () {
            flyWeightObj.delFile(id)
          }
          document.body.appendChild(dom)

          uploadDatabase[id] = {
            fileName: fileName,
            fileSize: fileSize,
            dom: dom
          }
          return flyWeightObj
        },

        setExternalState: function (id, flyWeightObj) {
          var uploadData = uploadDatabase[id]
          for (var i in uploadData) {
            flyWeightObj[i] = uploadData[i]
          }
        }
      }
    })()

現在,我們要觸發startUpload 操作了:

   var id = 0;

    window.startUpload = function (uploadType, files) {
      for (var i = 0, file; file = files[i++];) {
        var uploadObj = uploadManager.add(++id, uploadType, file.fileName, file.fileSize)
      }
    }

讓我們try一try最後的效果:

  startUpload('plugin', [
      {
        fileName: '1.txt',
        fileSize: 1000
      },
      {
        fileName: '2.txt',
        fileSize: 3000
      },
      {
        fileName: '3.txt',
        fileSize: 5000
      }
    ])

    startUpload('flash', [
      {
        fileName: '4.txt',
        fileSize: 1000
      },
      {
        fileName: '5.txt',
        fileSize: 3000
      },
      {
        fileName: '6.txt',
        fileSize: 5000
      }
    ])

6,小結
享元模式是一個很好的性能優化方案,但是因爲多了uploadManager和UploadFactory 對象,也會帶來維護問題。
享元模式適用場景:

  • 一個程序中大量使用相似對象
  • 對於使用大量對象,造成了很大的內存開銷
  • 對象的大多數狀態可以變爲外部狀態
  • 剝離對象的外部狀態後,可以用較少的共享對象取代大量對象
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章