木灵鱼儿

木灵鱼儿

阅读:385

最后更新:2022/06/14/ 1:24:00

结构型模式:享元模式

简介

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

举个例子:

我们有一个服装工厂,它有男装和女装衣服各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. 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。

版权申明

本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。

关于作者

站点职位 博主
获得点赞 0
文章被阅读 385

相关文章