享元模式Flyweight
用来解决大量且重复对象的管理问题。当内存受限的时候,使用享元模式来解决
大部分对象共享的问题。
GOF享元模式的定义为
使用共享的方式,让一群大小规模对象能更有效地运行。
本文需要先理解工厂模式和建造者模式
共享指的是使用管理结构来设计信息的存储方式,让可以被共享的信息,只需要产生一份对象,而这个对象能够被引用到其他对象中。
我们把哪些对象只能读取而不能写入的共享部分称为“内在状态”
把哪些不可共享的如当前生命力,等级,暴击,会随游戏运行而变化的称作外在状态。
享元模式提供的解决方案是:产生对象时,将能够共享的内在状态加以管理,并且将属于能自由更改的“外部 状态”也一起设置给新产生的对象中。
总的来说,就是内在状态就是对象不变,可以共享,外部状态就是对象经常变化,需要给其他类控制。
该如何实现享元模式呢?
书上根据士兵的属性:最大生命力,移动速度,属性名称等
是不会改变的属性,就设置成为共享组件类,成为属性类的内在状态。
第一步:确定可以共享的内在状态属性,将其定义成一个类对象
public class BaseAttr{
private int m_MaxHP;
private float m_MoveSpeed;
private string m_AttrName;
public BaseAttr(int MaxHP,float MoveSpeed,String AttrName){
this.MaxHP = MaxHP;
this.MoveSpeed = MoveSpeed;
this.AttrName = AttrName;
}
public int GetMaxHP(){
return m_MaxHP;
}
public float GetMoveSpeed(){
return m_AttrName;
}
public string GetAttrName(){
return m_AttrName;
}
}
属性基类是一个简单的常见属性类,字段属性在new对象的时候赋值,可以通过对象的get方法获得字段属性。
该类定义大的属性,就是不会更改的可共享的部分
该类通过建造者之外,没有任何方式来创建设置这些属性。
区分不可共享的类,这里列出了
角色基本属性类, 敌人基本属性类,我方式士兵基本属性类。
这些属性类都是会随场景的状态还变化。
但是它们(可共享,不可共享属性基类)的对象可以都在属性工厂引用(组合),被建造者设置。
基本角色属性类(不可变部分)也可以嵌套在角色基本属性接口类里(可变属性接口类),被角色基本角色接口类对象引用。
只是将共享的属性独立出去。
此时的属性接口类:
public abstract class ICharacterAttr{
protected BaseAttr m_BaseAttr = null; //不变的属性,可共享
protect int m_NowHP = 0; //可变的属性,不共享
protect IAttrStrategy m_AttrStrategy = null;//属性的计算策略
//基本的设置可变属性构造函数 获得方法。
//被保护是希望外部不可设置
protected void SetBaseAttr(BaseAttr BaseAttr){
m_BaseAttr = BaseAttr;
}
public void GetBaseAttr(BaseAttr BaseAttr){
return m_BaseAttr;
}
//策略类进行属性的修改
public void SetAttrStrategy (IAttrStrategy theAttrStrategy){
m_AttrStrategy = theAttrStrategy;
}
public void IAttrStrategy GetAttrStrategy(){
return m_AttrStrategy;
}
//获得基本属性的方法(可共享)
public virtual int GetMaxHP(){
return m_BaseAttr.GetMaxHP();
}
//回满当前HP的值
public void FullNowHP(){
m_NowHP = GetMaxHP();
}
public virtual string GetAttrName(){
return m_BaseAttr.GetAttrName();
}
//初始化角色属性
public virtual void InitAttr(){
m_AttrStrategy.InitAttr(this);
FullNowHP();
}
//....其他方法
}
该基本属性接口类需要子类才能使用。
书上定义了士兵属性类,和敌人属性类来实现。
public class SoldierAttr :ICharacterAttr{
protected int m_SoldierLV; //士兵等级
protected int m_AddMaxHP; //因为等级提升新增大的hp值
public SoldierAttr(){}
public void SetSoldierAttr(BaseAttr BaseAttr){
//共享组件
base.SetBaseAttr(BaseAttr);
//外部参数
m_SoldierLv = 1;
m_AddMaxHP = 0;
}
//...基本的获得Get属性方法
}
public class EnemyAttr : ICharacterAttr{
protected int m_CritRate = 0; //暴击概率
public EnemyAttr(){}
public void SetEnemyAttr(EnemyBaseAttr EnemyBaseAttr){
//共享组件
base.SetBaseAttr(EnemyBaseAttr);
//外部参数
m_CritRate = EnemyBaseAttr.InitCritRate;
}
public int GetCritRate(){
return m_CritRate;
}
public void CutdownCritRate(){
m_CriteRate -= m_CritRate/2;
}
}
两个属性子类包含因游戏而变化的属性:等级 ,新增的生命力,暴击概率等
并提供方法让外界能够修改它们。
接下来就是把属性放到工厂类来使用了。
运用享元模式的目的是为了减少内存使用,对内存使用的情况一般就是大量生产的内存对象情况才能明显的降低内存,因此可以一般在工厂(大量生产某种属性对象)中进行生产共享属性的具体设置。
运用享元模式的属性工厂AttrFactor 包含了3个Dictionary容器,分别管理记录游戏中3个属性对象。
public class AttrFactory : IAttrFactory{
private Dictionary <int,BaseAttr> m_SoldierAttrDB = null;
private Dictionary <int,EnemyBaseAttr> m_EnemyAttrDB = null;
private Dictionary <int,WeaponAttr> m_WeaponAttrDB = null;
public AttrFactory(){
InitSoldierAttr();
InitEnemyAttr();
InitWeaponAttr();
}
private void InitSoldierAttr)(){
m_soldierAttrDB = new Dictionary<int,BaseAttr>();
m_SoldierAttrDB.Add(1,new BaseAttr(10,3.0f,"新兵"));
m_SoldierAttrDB.Add(2,new BaseAttr(20,3.2f,"中士"));
m_SoldierAttrDB.Add(3,new BaseAttr(30,3.4f,"上尉"));
m_SoldierAttrDB.Add(4,new BaseAttr(3,0,"勇士"));
}
//..同理产生 InitEnemyAttr(); InitWeaponAttr();的方法这里省略
}
这里就是产生的共享属性类对象工厂,这里的共享属性类对象存在字典容器里,让后续的工厂能够在产生属性对象过程中获取对应的共享属性对象
继续看如何获取这些共享属性对象
public Class AttrFactory : IAttrFactory{
//获得Soldier的属性
public override SoldierAttr GetSoldierAttr(int AttrID){
if(m_SoldierAttrDB.ContainsKey(AttrID) == false){
Debug.LogWarning("GetSoldierAttr:AttrID["+AttrID+"]属性不存在");
return null;
}
SoldierAttr NewAttr = new SoldierAttr();
NewAttr.SetSoldierAttr(m_SoldierAttrDB[AttrID]);
return NewAttr;
}
//同理这里获取其他类型共享属性对象
}
注意获取共享属性需要先判断共享属性工厂类的容器里是否有这种共享属性对象。
没有就报出警告,有就返回这个共享属性的对象。
这里将工厂属性基类的属性分开成一个个方法,就可以有多种共享属性同时存在 ,如武器共享属性和角色士兵共享属性同时给士兵对象上。
享元模式的就是可以让可共享的对象分离出来,让多个对象可以同时使用这个对象,从而避免减少了内存的销毁,尤其是在大量产生这种对象的时候。
这样就不像旧方法那样产生重复的对象而增加内存的负担,对于游戏性能与所提升。
享元模式在游戏开发中,最常用到的就是属性系统。
游戏中的属性各种各样,如人物属性,敌人属性,材料属性,武器装备属性,宠物属性等,但一般一个类就几条字段,也就符合了GoF的定义的”一大群小规模对象”
当出现有大量属性数据时,作者建议将这些属性设计分开建档,属性工厂改为读取各个文件,将每一笔数据取出,再建立共享属性组件。
加载时间长时,可以使用延后初始化策略,当某一个属性被需要使用到时,才执行读入属性设置的操作,可以省去前期加载的时间。这样当角色身上只使用5、6个道具时,只需要初始化这5,6个道具属性,这样对应用程序的内存使用,也会有明显的优化。
对象池也运用了享元模式的思想,将子弹对象存在容器里。这样就可以只产生少量子弹对象,而不必要重复创建删除子弹对象,减少了GC操作,做到了性能的优化