对象池的管理与设计
一、什么是对象池
对象池模式(The Object Pool Pattern)是单例模式的一个变种,它提供了获取一系列相同对象实例的入口。当创建过的对象被释放时,这个对象会被放入设计好的池内而非直接销毁,在下次调用此类对象时,会直接去池子中获取已经初始化好的对象,直接省去再次创建对象所消耗的内存。
二、游戏开发实例
拿割草类游戏举例。在一个游戏场景中创建大量怪物,大概100只。玩家使用群攻技能消灭后,完成怪物对象的清除释放。后续又继续加入100只怪物任由玩家攻击。如果使用了对象池,后续的100只怪物并不需要重新创建,只需在先前被消灭的怪物池中复用即可。
三、对象池的管理设计
对象池的管理方式有很多,主要针对对象的类别创建,池子的销毁释放进行管理。这里有一种利用简单工厂和观察者模式进行对象池管理的策略,以下将会此设计进行详细解构:
对象:Obj类
对象。这里需要注意的是,对象池中的访问均是以object类型的key-value实现访问的,这个key可以采用对象生成的hashCode实现,但是Javascript对象不提供hashCode,所以我们借助白鹭引擎Egret中的***egret.HashObject***类来实现。其他语言如有hashCode或给予特定标识key值,则可以无视。由Iobj接口实现,源码如下:
interface IObj
{
hashc:number; //由于无法重写其get方法,这里只好定义属性进行访问
type:number; //类型标识
isIdle:boolean; //标记是否空闲
dispose():void; //释放对象内部引用
del():void; //彻底释放对象
reset():void; //重置
setProtocol(val:IDistributor):void; //设置协议
action():void; //动作
}
/** 对象类型枚举 */
enum ObjType {
player,
teammate,
enemy
}
/** 玩家对象类(实例) */
class Player extends egret.HashObject implements IObj{
private _isIdle:boolean = true;
private _type:number = ObjType.player;
private _dis:IDistributor = null;
private _message:string = "";
public constructor() {
super();
}
public reset():void
{
this._isIdle = false;
this._dis.distribution( this );
this._message = "我是玩家";
}
public dispose():void
{
this._isIdle = true;
this._dis.distribution( this );
//other code
}
public del():void
{
this.dispose();
this._dis = null;
}
public setProtocol( val:IDistributor ):void
{
this._dis = val;
}
public get type():number
{
return this._type;
}
public get hashc():number
{
return this.hashCode;
}
public get isIdle():boolean
{
return this._isIdle;
}
public action():void
{
console.log( this._message );
}
}
//队友对象类、敌人对象类略...
分配器:Distributor类
对象分配器。只负责对象的存储管理,同时作为对象的协议实现。由以下IDistributor接口实现,源码如下:
interface IDistributor
{
distribution(val:IObj):void; //分配
addVO(val:IObj):void; //添加元素
getVO(type:number):IObj; //获取元素
clear():void; //清除所有未使用的对象
}
class Distributor implements IDistributor
{
private _UsedPool:Object = null; //使用中的对象
private _IdlePool:Object = null; //未使用的对象
public constructor()
{
this._IdlePool = {};
this._UsedPool = {};
}
public distribution( val:IObj ):void
{
if( val.isIdle )
{
this._IdlePool[ val.hashc ] = val;
delete this._UsedPool[ val.hashc ];
}
else
{
this._UsedPool[ val.hashc ] = val;
delete this._IdlePool[ val.hashc ];
}
}
public addVO( val:IObj ):void
{
val.setProtocol( this );
if( val.isIdle )
{
this._IdlePool[ val.hashc ] = val;
}
else
{
this._UsedPool[ val.hashc ] = val;
}
}
public getVO( type:number ):IObj
{
let obj:IObj = null;
for (let key in this._IdlePool) {
obj = this._IdlePool[key] as IObj;
if ( obj.type == type ) {
obj.reset();
return obj;
}
}
return null;
}
public clear():void
{
let obj:IObj = null;
for (let key in this._IdlePool) {
obj = this._IdlePool[key] as IObj;
obj.del();
}
this._IdlePool = null;
this._IdlePool = {};
}
}
分配器内分有两个池,被使用池和空闲池。当用户需要新的对象时,会在空闲池中寻找,存在同类对象则会初始化后返回,同时加入被使用池中。不存在同类对象返回空值,以此告知生成器需要生成新的对象。
一旦对象不再被使用,则放入空闲池中。
clear方法可以清除空闲池中所有的对象,达到清除内存的目的。
生成器:ObjGenerator类
对象生成器。使用简单工厂模式,按类型给对象进行创建生成,不参与创建后的对象存储分配,只开放对象的获取方法。源码如下:
class ObjGenerator
{
private _dis:IDistributor = null;
public constructor( val:IDistributor )
{
this.init( val );
}
private init( val:IDistributor ):void
{
this._dis = val;
}
public getObj( type:number ):IBall
{
let vo:IObj = this._dis.getVO( type );
if( vo == null )
{
vo = this.createVO( type );
this._dis.addVO( vo );
vo.reset();
}
return vo;
}
private createVO( type:number ):IObj
{
switch( type )
{
case ObjType.player:
return new Player(); //主角
case ObjType.teammate:
return new TeamMate(); //队友
case ObjType.enemy:
return new Enemy(); //敌人
}
}
}
客户端演示
class Main extends egret.DisplayObjectContainer
{
public constructor() {
super();
this.init();
}
private _gen:ObjGenerator = null;
private init()
{
this._gen = new ObjGenerator(new Distributor());
let role:Player = this._gen.getBall(ObjType.player) as Player; //生成玩家对象,对象被使用
role.action(); //玩家执行动作逻辑
role.dispose();//不再使用,加入空闲池
role = null; //玩家对象销毁
let role2:Player = this._gen.getBall(ObjType.player) as Player; //再次生成玩家对象,但此时的对象是在空闲池中返回,可对比role的key值进行验证
}
}
四、总结
利用简单工厂和观察者模式,可以将对象池中对象生成与存储分配进行分离。用户只需操作对象的生成,将内存持久化权限交由分配器处理。在必要的时候还可以使用分配器提供的clear方法彻底释放内存(例如游戏切换场景,关卡结束)。