结构型模式:享元模式
简介
享元模式是运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
举个例子:
我们有一个服装工厂,它有男装和女装衣服各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(); //拍照
}
此时你会发现,不管衣服有多少件,我们永远只有两个模特:男模特和女模特,避免的大量重复对象的创建,这极大的节省了性能开销。
其次为了更好的开闭,声明了享元对象的抽象,大家都依赖于抽象,从而具体的实现可以更加灵活的替换了,只要满足要求即可。
扩展
有的时候我们的操作可能不是同步的,我们将非享元的数据保存起来,通过一个外部状态管理器进行管理,比如将对象划分为:享元对象、非享元对象。使用的时候让他们进行组合使用。
比如,有的数据可能会有很多个,但是是由客户手动触发的,那么肯定是在触发时才进行组合,那么我们就可以先将非享元的数据存储起来,使用时再去获取。
应用场景
享元模式是通过减少内存中对象的数量来节省内存空间的,所以以下几种情形适合采用享元模式。
- 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
- 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
- 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
暂无评论数据