[开发者心得] 设计模式分享 对象池

[复制链接]
903 |0
剑寂万古 发表于 2023-6-5 17:22:01 | 显示全部楼层 |阅读模式
本帖最后由 剑寂万古 于 2023-6-5 17:32 编辑

对象池概述
主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

介绍
意图:运用共享技术有效地支持大量细粒度的对象。
主要解决:在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
何时使用: 1、系统中有大量对象。 2、这些对象消耗大量内存。 3、这些对象的状态大部分可以外部化。 4、这些对象可以按照内部状态分为很多组,组内信息相对独立且对象状态可以重置。5、系统不依赖于这些对象身份,这些对象是不可分辨的。
优点:大大减少对象的创建,降低系统的内存,使效率提高。
缺点:1、不恰当的使用对象池可能导致系统中有大量无用对象占据内存空间.2、对象可能被污染,需要回收的时候重置一下状态。
使用场景: 1、系统有大量相似对象。 2、需要缓冲池的场景。
注意事项: 对象池必须有一个工厂对象加以控制。

简单对象池

class Item{
    private state:number=0;
    constructor(a,b,c,d,e){
    }
   
    public changeState(state:number){
        this.state=state;
    }
    public setParam()
    /**
    *重置对象状态
    */
    public reset(){
        this.state=0;
    }
}
//得到一个item对象
this.item=ItemPool.instance.spawn(a,b,c,d,e);

//回收一个对象
ItemPool.instance.unSpawn(this.item);

class ItemPool{
    //单例方法
    private static _instance:ItemPool;
    public static get instance(){
        return ItemPool._instance;
    }
   
    //存储列表
    private pool:Item[]=[];
   
    /**
    *获得一个对象
    */
    public spawn(...params):Item{
        return this.pool.length>0?this.pool.pop():new Item(...params);
    }
   
    /**
    *回收一个对象
    */
    public unSpawn(item:Item){
        item.reset();
        this.pool.push(item);
    }
}



- 缺点
  - 对象工厂不可复用,每种对象对应一个对象工厂
  - 对象需要保存,回收的时候需要记得调用对象工厂unSpawn方法,否则会内存泄漏
- 补充
  - 针对忘记调用回收方法导致的内存泄漏,可以增加一个警告来避免,采取跟踪对象池对象数量的方式可以很容易的做到这一点


class ItemPool{
   ...
   
   //当前对象数量
   private currentCount:number=0;
   //警告阈值
   private warningCount:number=1000;
    /**
    *获得一个对象
    */
    public spawn(...params):Item{
        if(this.currentCount>this.warningCount){
            //数量超限,发出警告
            console.warning("对象数量超限");
        }
        return this.pool.length>0?this.pool.pop():{this.currentCount++,new Item(...params);}
    }
   
    ...
}


  - 如果存在潮汐对象,例如场景上动态创建1000个CUBE,但其中900个会在很短时间销毁且长时间不再复用,可以加入自动释放的机制


class ItemPool{
   ...
   
   //释放阈值
   private destoryCount:number=100;
   /**
    *回收一个对象
    */
    public unSpawn(item:Item){
        if(this.pool.length>this.destoryCount){
            //达到释放阈值,直接释放
            item.destroy();
        }else{
            //未达到释放阈值,回收到对象池
            item.reset();
            this.pool.push(item);
        }
    }
   
}


泛型对象池

interface Item {
    /**
    *重置对象状态
    */
    reset();

}
class ItemA implements Item{
    public reset(){
    }
    ...
    public methodA(){
    }
}
class ItemB implements Item{
    public reset(){
    }
    ...
    public methodB(){
    }
}

const poolA=new ItemPool(ItemA);
poolA.spawn().methodA();

const poolB=new ItemPool(ItemB);
poolB.spawn().methodB();

class ItemPool<T extends Item>{
    //泛型支持
    constructor(private cls: { new(...params): T }) {

    }
    //存储列表
    private pool: T[] = [];

    /**
    *获得一个对象
    */
    public spawn(...params): T {
        return this.pool.length > 0 ? this.pool.pop() : new this.cls(...params) as T;
    }

    /**
    *回收一个对象
    */
    public unSpawn(item: T) {
        item.reset();
        this.pool.push(item);
    }

}


- 优点
  - 加入了泛型支持,对象工厂可以共用一套代码
  - 泛型代码提示可以提示子类的方法
- 缺点
  - 需要有一个存储对象工厂的工厂



class ItemPoolFactory{
    public static readonly PoolA=new ItemPool(A);
    public static readonly PoolB=new ItemPool(B);
    ...
    /**
    *获取任意对象池对象
    */
    private static getPool(cls:{ new(...params): T }){
        return new ItemPool(cls);
    }
}


  - 对象需要保存,回收的时候需要记得调用对象工厂unSpawn方法,否则会内存泄漏

自回收对象池


interface Item {
    /**
    *重置对象状态
    */
    reset();
    /**
    *回收对象
    */
    destroy();
}

class ItemA implements Item {
    //保存对象池实例用作回收
    constructor(private pool: ItemPool<Item>) {
    }
    reset() {
    }
    /**
    *销毁对象,这里实际是回收
    */
    public destroy() {
        this.reset();
        this.pool.cache.push(this);
    }
}

//得到一个item对象
this.item=ItemPool.instance.spawn(a,b,c,d,e);

this.item.destroy();

class ItemPool<T extends Item>{
    //泛型支持
    constructor(private cls: { new(pool: ItemPool<T>, ...params): T }) {

    }
    //存储列表
    private pool: T[] = [];

    public get cache() {
        return this.pool;
    }
    /**
    *获得一个对象
    */
    public spawn(...params): T {
        return this.pool.length > 0 ? this.pool.pop() : new this.cls(this, ...params) as T;
    }
}



- 优点
  - 加入了泛型支持,对象工厂可以共用一套代码
  - 泛型代码提示可以提示子类的方法
  - 回收方法在对象自己身上
- 缺点
  - 需要有一个存储对象工厂的工厂
  - 复杂的情况下可能出现循环引用


限定对象池


class Item{
    constructor(a,b,c){
    }
   
    /**
    *重置对象状态
    */
    public reset(){
    }
}

class ItemPool{
    //单例方法
    private static _instance:ItemPool;
    public static get instance(){
        return ItemPool._instance;
    }
   
    //对象索引
    private cacheIndex:number=0;
    //对象最大值
    private cacheCount:number=10;
   
    //存储列表
    private pool:Item[]=[];
   
    constructor(){
        
    }
    /**
    *获得一个对象
    */
    public spawn(...params):Item{
        if(this.pool.length<this.cacheCount){
            const item=new Item(...params);
            this.pool.push(item);
            return item;
        }
        if(this.cacheIndex==this.pool.length){
            this.cacheIndex=0;
        }
        return this.pool[this.cacheIndex++];
    }
   
    /**
    *回收一个对象
    */
    public unSpawn(item:Item){
        item.reset();
    }
}



- 使用场景
  - 针对限定数量的对象适用,例如全场特效数量控制
  - 可以和前面的普通对象池使用方式结合
- 优点
  - 限定了对象数量,性能上有保证
- 缺点
  - 可能在对象使用时被复用,影响表现效果


享元对象池


class A{
    private draw:Drawer;
     constructor(){
        this.draw=DrawerPool.getPool("A");
    }
}
const a1=new A();
const a2=new A();
...

class B{
    private draw:Drawer;
     constructor(){
        this.draw=DrawerPool.getPool("B");
    }
}
class C{
    private draw:Drawer;
   
    constructor(){
        this.draw=DrawerPool.getPool("C");
    }
}
class Drawer{
}
class DrawerA extends Drawer{
    //超级占内存的对象
    private aBigMemoryObjectA;
    constructor(){
   
    }
}
class DrawerB extends Drawer{
    //超级占内存的对象
    private aBigMemoryObjectB;
    constructor(){
   
    }
}
class DrawerPool{
    private pool:Map<string,Drawer>=new Map();
   
    //获取对象池对象
    public getPool(tag:string){
        return this.pool.get(tag);
    }
}



- 使用场景
  - 将可复用的对象抽取为一个对象池。
- 优点
  - 可以将占用内存较多的对象进行复用
- 缺点
  - 享元对象(Drawer)中不能存在外部逻辑,不能保存状态,否则会出现污染
  - 可能导致循环引用



回复

使用道具 举报

热门版块
快速回复 返回顶部 返回列表