JavaScript设计模式-结构型-享元模式

2021-07-01 JavaScript设计模式 阅读 728 次

概要

享元模式是一种结构型设计模式, 它摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象。

享元(flyweight)模式是一种用于性能优化的模式,“fly”在这里是苍蝇的意思,意为蝇量级。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。

如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就非常有用了。在JavaScript 中,浏览器特别是移动端的浏览器分配的内存并不算多,如何节省内存就成了一件非常有意义的事情。

JavaScript设计模式-结构型-享元模式-黄继鹏博客

问题

小黄负责公司测试机器的管理工作,包括购买新机,借出和归还等工作,在机型不多的情况下,为了管理好,他通过以下程序来进行管理

class Device {
  // 记录设备的基本信息
  constructor(id, memory, frequency, processor, network, pixel, price) {
    this.id = id;
    this.memory = memory;
    this.frequency = frenquency;
    this.processor = porcessor;
    this.network = network;
    this.pixel = pixel;
    this.price = price;
    // more attributes ...
  }
  checkout() {
    this.hasCheckedOut = true;
    this.checkoutDate = new Date();
  }
  giveback() {
    this.hasCheckedOut = false;
    this.checkoutDate = null;
  }
  // more methods ...
}

代码还算是比较简单明了的,每次购买机器入库都会执行:

let device = new Device(...);
DB.insert(device);

有人借还设备时,便会执行:

let device = Database.query(deviceId);

// 借出
device.checkout();

// 归还
device.giveback();

DB.update(device);

但是随着团队变大,人员增多,设备的需求量急剧上升。测试标准也有所提升,要求覆盖国内所有常见机型,大约 50 多种,每个机型需要购置大约 100 台机器,总量有 5000 多台。

这个时候小黄头都大了,每台设备的描述信息和借还记录信息都储存在各自的 device 对象中,每个对象大约需要占用 10kb 的内存,5000 多台设备,算下来需要占用 50Mb 的内存。几千个大对象放在内存中,这种数据管理模式需要持续的大内存,会带来一系列问题。

解决方案

定义数据模型,一个 Device 对象代表一种型号的设备:

class Device {
  constructor(type, xxx, ..) {
    this.type = type;
    // 一种设备的全部基本信息
    this.xxx = xxx;
  }
}

使用 DevicePool 管理各种型号的设备:

class DevicePool {
  // hash 表保存设备信息
  devicePool = {};
  create(type, xxx, ...) {
    if (devicePool[type]) return devicePool[type];
    const device = new Device(type, xxx, ...);
    devicePool[type] = device;
    return device;
  }
}

设备的借还信息和其他信息,通过 DeviceManager 来管理:

class DeviceManager {
  deviceManager = {};
  // 借出时,添加借出记录
  checkout(id, type, xxx, ...) {
    const device = new DevicePool.create(type, xxx, ...);
    deviceManager[id] = {
      device: device,
      hasCheckedOut = true,
      checkoutDate = new Date();
    };
  }
  // 归还时,删除借出记录
  giveback(id, type) {
    deviceManager[id].hasCheckedOut = false;
    deviceManager[id].checkoutDate = null;
  }
}

设备对象从 50Mb 下降到了 2Mb

同一类型的设备描述是相同的,没必要在每个对象中都保存一份。使用单例模式储存设备元信息(Device 类),使用工厂模式复用一个类型的设备描述信息(DevicePool 类)

通过 DevicePool 和 DeviceManager 将数据元信息和数据动态信息进行了有效的隔离,然后通过 checkout 和 giveback 两个方法灵活地组合了这两类信息,组合的时候,有效地控制了内存的无效复制。

总结

享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。

在享元模式中有两个比较重要的关键词,内部变量和外部变量;内部变量是可以共享的属性集,而外部变量是对象之间的差异部分,通过相同+不同的方式组合诸多对象,可以有效地节省系统空间,降低内存大小。

优缺点

  • 优点
    • 享元模式的优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份。
    • 享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
  • 缺点
    • 享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
    • 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。

适合应用场景

  • 一个系统有大量相同或者相似的对象,由于这类对象的大量使用,造成内存的大量耗费。
  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
  • 使用享元模式需要维护一个存储享元对象的享元池,而这需要耗费资源,因此,应当在多次重复使用享元对象时才值得使用享元模式。
0条评论
...