美文网首页
享元模式

享元模式

作者: 般犀 | 来源:发表于2019-02-16 14:10 被阅读0次

享元模式解决的痛点是当一个操作需要创建无数多个对象时,对象数量过多的问题。

如果我们可以在这个操作中提取出内部对象和外部对象,就能在只创建等于内部对象个数的对象的情况下,实现相同的目标。

一个例子是,假设一个服装厂有新款男装50件,新款女装50件,需要为每一件衣服都拍一张模特照。

如果用程序模拟这种情况:

var Model  = function(sex, underwear) {
  this.sex = sex;
  this.underwear = underwear;
}
Model.prototype.takePhoto = function() {
  console.log(`sex=${this.sex}, underwear = this.underwear`);
}
for (var i = 1; i < 50; i++) {
  var model = new Model('male', i);
  model.takePhoto();
}
for (var i = 1; i < 50; i++) {
  var model = new Model('female', i);
  model.takePhoto();
}

这种场景是:叫了100个模特试穿了100件衣服,程序一共创建了100个对象。如果要试穿的是1000件衣服,无疑产生的内存开销是巨大的。
实际上,我们只需要一男一女两个模特,每个人穿50件衣服即可。

在这个过程中,可以被重复使用的模特就是内部状态,不可复用的衣服就是外部状态。
这两者的区别是:

  • 内部状态存储于对象内部;
  • 内部状态可以被一些对象共享;
  • 内部状态独立于具体的场景,通常不会改变;
  • 外部状态取决于具体的场景,并根据场景变化,外部状态不能被共享。

享元模式的实际应用——文件上传

1. 对象爆炸

在做文件上传功能时,虽然可以按照队列一个一个地排队上传,但也支持同时选择2000个文件同时上传。每一个文件都对应着一个 JS 上传对象的创建。如果同时 new 2000个对象,显然是不太合理的。
此外,上传还会支持几种上传方式,比如插件上传,flash上传等,这里我们只以插件上传和 flash 上传为例,当用户选择了上传模式后,插件和flash都会通知调用 Window 下的一个全局 JavaScript 函数——startUpload,用户选择的文件列表被组合成一个数组 files 塞进该函数的参数列表里。代码如下:

var id = 0;
window.startUpload = function (uploadType, files) { // uploadType 区分是控件还是flash
  for (var i = 0, file; file = files[i++];) {
    var uploadObj = new Upload(uploadType, file.fileName, file.fileSize);
    uploadObj.init(id++); // 给upload 对象设置一个唯一的id
  }
};
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);
};

接下来实现文件的删除功能,当被删除的文件小于3000KB时可以直接删除,否则弹出一个提示框,提示用户是否要删除该文件:

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);
  }
};

接下来分别创建3个插件上传对象和3个Flash上传对象:

startUpload('plugin', [{
    fileName: '1.txt',
    fileSize: 1000
  },
  {
    fileName: '2.html',
    fileSize: 3000
  },
  {
    fileName: '3.txt',
    fileSize: 5000
  }
]);
startUpload('flash', [{
    fileName: '4.txt',
    fileSize: 1000
  },
  {
    fileName: '5.html',
    fileSize: 3000
  },
  {
    fileName: '6.txt',
    fileSize: 5000
  }
]);
2. 享元模式重构文件上传

上一节代码是第一版的文件上传, 有多少个需要上传的文件,就一共创建了多少个Upload对象,接下来用享元模式重构它。
首先提取内部状态和外部状态。可以发现,除了 UploadType 可以被提取为 插件上传 和 Flash 上传,其他状态都不是固定的,所以文件大小,文件名等都是外部状态。

2.1 剥离外部状态

明确了UploadType是内部状态后,我们就要把其他状态从构造函数中抽离出来,Upload 构造函数中只保留 uploadType 参数:

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

Upload.prototype.init函数也不再需要,因为 upload 对象初始化的工作被放在了 uploadManager.add 函数里面,接下来只需要定义Upload.prototype.del函数即可:

Upload.prototype.delFile = function (id) {
  uploadManager.setExternalState(id, this); // (1)
  if (this.fileSize < 3000) {
    return this.dom.parentNode.removeChild(this.dom);
  }
  if (window.confirm('确定要删除该文件吗? ' + this.fileName)) {
    return this.dom.parentNode.removeChild(this.dom);
  }
};

在开始删除文件之前,需要读取文件的实际大小,而文件的fileSize被储存在外部管理器uploadManager中,所以需要uploadManager.setExternalState方法给共享对象设置正确的fileSize,上段的代码(1)处表示把当前 id 对应的对象的外部状态,从外部管理器中拿过来,组装到共享对象中。

工厂进行对象实例化

定义一个工厂来创建 upload 对象,如果某种内部状态对应的共享对象已经被创建过,那么直接返回这个对象,否则创建一个新的对象:

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

这里是利用了闭包,把已经存在的共享对象保存在createdFlyWeightObjs中。

管理器封装外部状态

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);
  }
};

最后是测试时间,运行第一版的测试代码,可以发现运行结果跟用享元模式重构之前一致。

通过享元模式,即使需要上传的文件有2000个,实际创建的对象也只有2个(插件上传对象,flash上传对象)。

对象池

对象池可以实现对象的回收再利用。创建一个对象之后,如果不需要,可以回收进一个数组中,下次要用到的时候,再从数组中取出。避免了对象的重复创建。

相关文章

  • 设计模式之享元模式(flyweight模式)

    引入享元模式 享元模式的实例 享元模式的分析 引入享元模式 flyweight是轻量级的意思,指的是拳击比赛中选手...

  • 第4章 结构型模式-享元模式

    一、享元模式的简介 二、享元模式的优缺点 三、享元模式的实例

  • 享元模式

    一、享元模式介绍 二、享元模式代码实例

  • 设计模式--享元模式

    目录 本文的结构如下: 引言 什么是享元模式 模式的结构 典型代码 代码示例 单纯享元模式和复合享元模式 模式扩展...

  • 享元模式C++

    享元模式,就是运用共享技术有效地支持大量细粒度的对象。 享元模式结构图 享元模式基本代码 应用场景 享元模式可以避...

  • 设计模式之——享元模式

    1 享元模式的定义 享元模式:使用共享对象可有效地支持大量细粒度的对象。享元模式是池技术的重要实现方式。享元模式的...

  • 好程序员Java培训​分享java设计模式之享元模式

    好程序员Java培训​分享java设计模式之享元模式,Java设计模式中的享元模式。享元模式有点类似于单例...

  • 结构型模式:享元模式

    文章首发:结构型模式:享元模式 七大结构型模式之六:享元模式。 简介 姓名 :享元模式 英文名 :Flyweigh...

  • Java设计模式(三)

    talk is cheap show me the code 享元模式 享元模式 责任链模式 责任链模式Filte...

  • 结构型-Flyweight

    享元模式原理与实现 享元模式(Flyweight Design Pattern) 所谓“享元”,顾名思义就是被共享...

网友评论

      本文标题:享元模式

      本文链接:https://www.haomeiwen.com/subject/ajmbeqtx.html