简介

享元模式是运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。

举个例子:

我们有一个服装工厂,它有男装和女装衣服各100套,为了销售,需要模特来帮忙拍照做成广告照片,正常情况下我们可能会这么写。

class Model {
  private gender: string; //性别
  private clothes: number; //衣服

  constructor(gender: string, clothes: number) {
    this.gender = gender;
    this.clothes = clothes;
  }

  //拍照
  takingPictures(): void {
    console.log(`${this.gender}模特,穿了第${this.clothes}件衣服,拍了一张照片`);
  }
}

//男模特拍照100件衣服
for (let i = 0; i < 100; i++) {
  const model = new Model("男", i + 1);
  model.takingPictures();
}

//女模特拍照100件衣服
for (let i = 0; i < 100; i++) {
  const model = new Model("女", i + 1);
  model.takingPictures();
}

我们可以发现总共创建了200个Model的实例,如果衣服再多,随着数量级的上升,会带来极大的性能消耗。

这点在游戏制作方面尤为明显。

而享元模式就是用于优化性能的,它将公共的部分抽离出来作为一个共享对象,使用的时候都使用该共享对象,而不用重新创建新的对象,从而减少了性能的消耗。

作为一个共享的对象,如果内容是一成不变的那肯定不符合我们的业务需求,就拿上面的来说,我们的模特数据有两个:性别和衣服;而享元模式中,数据被分为:内部状态和外部状态。

如果我们创建了一个男模特作为一个共享对象,它的性别是不是可以视为内部状态,因为性别不用每次都改变,这也符合了享元模式中对于内部状态的定义:内部状态指对象共享出来的信息,存储在享元信息内部,并且不回随环境的改变而改变;

而男模特的衣服是一个动态变化的,所以它是一个外部状态,外部状态的定义:外部状态指对象得以依赖的一个标记,随环境的改变而改变,不可共享。

现在我们明确的知道,男模特和女模特是可以作为一个共享对象的,其中性别是内部状态,衣服是外部状态,那么如何正确的获取这个共享对象,这里就需要一个角色享元工厂;它其实就是一个单例工厂,每次都从享元工厂获取享元对象,然后将非享元的数据(衣服)与对象合并;然后使用。

代码实现

//享元对象的抽象,用于规范代码
abstract class ModelAbstract {
  gender: string; //性别
  clothes: number | undefined; //衣服

  constructor(gender: string) {
    this.gender = gender;
  }

  abstract wearClothes(clothes: number): void;

  abstract takingPictures(): void;
}

//享元对象
class Model extends ModelAbstract {
  constructor(gender: string) {
    super(gender);
  }

  //穿衣服的接口
  wearClothes(clothes: number): void {
    this.clothes = clothes;
  }

  //拍照
  takingPictures(): void {
    if (typeof this.clothes !== "number") {
      throw new Error(`衣服出现问题了:${this.clothes}`);
    }
    console.log(`${this.gender}模特,穿了第${this.clothes}件衣服,拍了一张照片`);
  }
}

//享元工厂
class ModelFactory {
  private static models: { [key: string]: ModelAbstract } = {};

  private constructor() {}

  public static createModel(gender: string): ModelAbstract {
    if (!ModelFactory.models[gender]) {
      ModelFactory.models[gender] = new Model(gender);
    }

    return ModelFactory.models[gender];
  }
}

//男模特拍照100件衣服
for (let i = 0; i < 100; i++) {
  const model: ModelAbstract = ModelFactory.createModel("男");
  model.wearClothes(i + 1); //穿衣服
  model.takingPictures(); //拍照
}

//女模特拍照100件衣服
for (let i = 0; i < 100; i++) {
  const model: ModelAbstract = ModelFactory.createModel("女");
  model.wearClothes(i + 1); //穿衣服
  model.takingPictures(); //拍照
}

此时你会发现,不管衣服有多少件,我们永远只有两个模特:男模特和女模特,避免的大量重复对象的创建,这极大的节省了性能开销。

其次为了更好的开闭,声明了享元对象的抽象,大家都依赖于抽象,从而具体的实现可以更加灵活的替换了,只要满足要求即可。

扩展

有的时候我们的操作可能不是同步的,我们将非享元的数据保存起来,通过一个外部状态管理器进行管理,比如将对象划分为:享元对象、非享元对象。使用的时候让他们进行组合使用。

比如,有的数据可能会有很多个,但是是由客户手动触发的,那么肯定是在触发时才进行组合,那么我们就可以先将非享元的数据存储起来,使用时再去获取。

应用场景

享元模式是通过减少内存中对象的数量来节省内存空间的,所以以下几种情形适合采用享元模式。

  1. 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
  2. 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
  3. 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。
分类: 设计模式 标签: 设计模式单例模式结构型模式享元模式享元对象非享元对象享元工厂

评论

暂无评论数据

暂无评论数据

目录