关于享元模式我所知道的

81 阅读1分钟

关键词:结构型 享元 FlyWeight 性能优化

什么是享元模式?

享元的本质是通过 共享基础类 来处理大量相同的对象。主要是用于性能优化

先看一个简单案例对享元模式有个概念,

需求:现在需要做50串烤肉,50串烤玉米。一般情况下需要准备100根签子串烤串。

但是今天店里只有两根签子,所以我们只能两根签子这样一批批上。

这就是享元模式,拿时间换内存。

// 签子足够的时候
class Skewers {
  constructor(type, skewerIdx) {
    this.type = type;
    this.skewerIdx = skewerIdx;
  }
  showInfo() {
    console.log(`第${this.skewerIdx}${this.type}签`);
  }
}

for (let i = 0; i < 50; i++) {
  const m = new Skewers("肉", i + 1);
  m.showInfo();
}

for (let i = 0; i < 50; i++) {
  const f = new Skewers("蔬菜", i + 1);
  f.showInfo();
}

// 场景优化后
class Skewers {
  constructor(type) {
    this.type = type;
  }
  showInfo() {
    console.log(`第${this.skewerIdx}${this.type}签`);
  }
}

const m = new Skewers("肉");
const f = new Skewers("蔬菜");

for (let i = 0; i < 50; i++) {
  f.skewerIdx = i + 1;
  m.skewerIdx = i + 1;
  f.showInfo();
  m.showInfo();
}

应用场景

判断是否需要使用享元模式:

  • 项目中存在大量重复对象
  • 重复的对象为不可变对象,或者一些属性是不可变的。

总而言之需要达到:复用对象,节省内存 的作用。

前端的话,比如:地图上的 cluster 和 mark 都是常用的场景,和绘图相关的都有可能使用到。

如何实现享元模式?

前提:享元对象是不可变对象。

核心:在内存中只保留一份实例,供多处代码引用。

  1. 将需要改写为享元类属性拆分为两个部分,
    • 内在状态:包含不变的、可在许多对象中重用的部分。
    • 外在状态:包含每个对象各自不同的情景数据的成员变量
  2. 保留类中表示内在状态的属性, 并设置为不可修改。这些属性的值只能在 constructor 中获取。
  3. 找到所有使用外在状态成员变量的方法,为方法中所用的每个成员变量新建一个参数, 并使用该参数代替成员变量。
  4. 创建工厂类来管理享元缓存池,它负责在新建享元时检查已有的享元。
// 享元模式
class Flyweight {
  // 内部状态
  private sharedState: any;

  constructor(sharedState: any) {
    this.sharedState = sharedState;
  }

  // 传入外部状态
  public operation(uniqueState): void {
    const s = JSON.stringify(this.sharedState);
    const u = JSON.stringify(uniqueState);
    console.log(`Flyweight: Displaying shared (${s}) and unique (${u}) state.`);
  }
}

// 工厂方法-生成多个实例
class FlyweightFactory {
  private flyweights: { [key: string]: Flyweight } = <any>{};
  constructor(initialFlyweights: string[][]) {
    for (const state of initialFlyweights) {
      this.flyweights[this.getKey(state)] = new Flyweight(state);
    }
  }
  private getKey(state: string[]): string {
    return state.join("_");
  }
  public getFlyweight(sharedState: string[]): Flyweight {
    const key = this.getKey(sharedState);

    if (!(key in this.flyweights)) {
      console.log(
        "FlyweightFactory: Can't find a flyweight, creating new one."
      );
      this.flyweights[key] = new Flyweight(sharedState);
    } else {
      console.log("FlyweightFactory: Reusing existing flyweight.");
    }

    return this.flyweights[key];
  }

  public listFlyweights(): void {
    const count = Object.keys(this.flyweights).length;
    console.log(`\nFlyweightFactory: I have ${count} flyweights:`);
    for (const key in this.flyweights) {
      console.log(key);
    }
  }
}

// 找到所有使用外在状态成员变量的方法,为在方法中所用的每个成员变量新建一个参数, 并使用该参数代替成员变量。
function addCarToPoliceDatabase(
  ff: FlyweightFactory,
  plates: string,
  owner: string,
  brand: string,
  model: string,
  color: string
) {
  const flyweight = ff.getFlyweight([brand, model, color]);
  flyweight.operation([plates, owner]);
}

const factory = new FlyweightFactory([
  ["Chevrolet", "Camaro2018", "pink"],
  ["Mercedes Benz", "C300", "black"],
  ["Mercedes Benz", "C500", "red"],
  ["BMW", "M5", "red"],
  ["BMW", "X6", "white"],
]);

factory.listFlyweights();
addCarToPoliceDatabase(factory, "CL234IR", "James Doe", "BMW", "M5", "red");
addCarToPoliceDatabase(factory, "CL234IR", "James Doe", "BMW", "X1", "red");
factory.listFlyweights();

参考资料

refactoringguru 享元模式

《JavaScript 设计模式与开发实践》