享元模式
使用共享的方式,让一大群小规模对象能更有效的运行。
享元模式的定义
定义的两个重点:“共享”与“一大群小规模对象”
首先,“一大群小规模对象”指的是:虽然有时候类的组成很简单,可能只有几个类型为 int 的类成员,但如果这些类成员的属性是相同而且可以共享的,那么当系统产了一大群类的对象时,这部分重复的部分就是浪费的,因为他们只需要存在一份即可。
参与者说明如下:
- FlyweightFactory(工厂类)
- 负责生产和管理Flyweight的组件
- 内部通常使用容器类来存储共享的Flyweight组件
- 提供工厂方法产生对应的组件,当产生的是共产组件时,就加入到Flyweight管理容器内。
- Flyweight(组件接口)
- 定义组件的操作接口
- ConcreteFlyweight(可以共享的组件)
- 实现Flyweight接口
- 产生的组件时可以共享的,并加入到Flyweight管理器中。
- UnsharedConcreteFlyweight(不可以共享的组件)
- 实现Flyweight接口,也可以选择不继承子Flyweight接口。
- 可以定义为单独的组件,不包含任何共享资源。
- 也可以将一些共享组件定义为类的成员,成为内部状态;并另外定义其他不被共享的成员,作为外部状态使用。
具体实现
// 可以被共用的Flyweight介面
public abstract class Flyweight
{
protected string m_Content; //顯示的內容
public Flyweight(){}
public Flyweight(string Content)
{
m_Content= Content;
}
public string GetContent()
{
return m_Content;
}
public abstract void Operator();
}
// 共用的元件
public class ConcreteFlyweight : Flyweight
{
public ConcreteFlyweight(string Content):base( Content )
{
}
public override void Operator()
{
Debug.Log("ConcreteFlyweight.Content["+m_Content+"]");
}
}
// 不共用的元件(可以不必繼承)
public class UnsharedCoincreteFlyweight //: Flyweight
{
Flyweight m_Flyweight = null; // 共享的元件
string m_UnsharedContent; // 不共享的元件
public UnsharedCoincreteFlyweight(string Content)
{
m_UnsharedContent = Content;
}
// 設定共享的元件
public void SetFlyweight(Flyweight theFlyweight)
{
m_Flyweight = theFlyweight;
}
public void Operator()
{
string Msg = string.Format("UnsharedCoincreteFlyweight.Content[{0}]",m_UnsharedContent);
if( m_Flyweight != null)
Msg += "包含了:" + m_Flyweight.GetContent();
Debug.Log(Msg);
}
}
// 負責產生Flyweight的工廠介面
public class FlyweightFactor
{
Dictionary<string,Flyweight> m_Flyweights = new Dictionary<string,Flyweight>();
// 取得共用的元件
public Flyweight GetFlyweight(string Key,string Content)
{
if( m_Flyweights.ContainsKey( Key) )
return m_Flyweights[Key];
// 產生並設定內容
ConcreteFlyweight theFlyweight = new ConcreteFlyweight( Content );
m_Flyweights[Key] = theFlyweight;
Debug.Log ("New ConcreteFlyweigh Key["+Key+"] Content["+Content+"]");
return theFlyweight;
}
// 取得元件(只取得不共用的Flyweight)
public UnsharedCoincreteFlyweight GetUnsharedFlyweight(string Content)
{
return new UnsharedCoincreteFlyweight( Content);
}
// 取得元件(包含共用部份的Flyweight)
public UnsharedCoincreteFlyweight GetUnsharedFlyweight(string Key,string SharedContent,string UnsharedContent)
{
// 先取得共用的部份
Flyweight SharedFlyweight = GetFlyweight(Key, SharedContent);
// 產出元件
UnsharedCoincreteFlyweight theFlyweight = new UnsharedCoincreteFlyweight( UnsharedContent);
theFlyweight.SetFlyweight( SharedFlyweight ); // 設定共享的部份
return theFlyweight;
}
}
public class FlyweightTest
{
void UnitTest () {
// 元件工廠
FlyweightFactor theFactory = new FlyweightFactor();
// 產生共用元件
theFactory.GetFlyweight("1","共用元件1");
theFactory.GetFlyweight("2","共用元件1");
theFactory.GetFlyweight("3","共用元件1");
// 取得一個共用元件
Flyweight theFlyweight = theFactory.GetFlyweight("1","");
theFlyweight.Operator();
// 產生不共用的元件
UnsharedCoincreteFlyweight theUnshared1 = theFactory.GetUnsharedFlyweight("不共用的資訊1");
theUnshared1.Operator();
// 設定共用元件
theUnshared1.SetFlyweight( theFlyweight );
// 產生不共用的元件2,並指定使用共用元件1
UnsharedCoincreteFlyweight theUnshared2 = theFactory.GetUnsharedFlyweight("1","","不共用的資訊2");
// 同時顯示
theUnshared1.Operator();
theUnshared2.Operator();
}
}
实现角色属性功能
需求:
- 最大生命力(MaxHP)、移动速度(Move Speed)、属性名称(AttrName)等
- 敌方阵营角色属性类(EnemyAttr)中的暴击率(InitCritRate)初始值。
- 角色现有生命力(NowHP),被攻击时减少
- 玩家阵营角色属性类(SoldierAttr)中,等级(SoldierLv)及生命力加成(AddMaxHp),会随兵营等级提升而改变角色的等级
- 敌方阵营角色属性类(SoldierAttr)中,暴击率(CritRate)现值,会因为成功发生暴击之后而减半
属性工厂IAttrFactory在运用享元模式后的类结构图
参与者说明如下:
- BaseAttr:定义角色属性中,不会变更可共享的部分。
- EnemyBaseAttr:敌方阵营的角色有暴击率的功能,用来强化攻击时的优势,按照游戏设计需求,必须开放给企划作设置,所以用一个新类来增加这个设置,而不在BaseAttr中增加。
- ICharacterAttr、SoldierAttr、EnemyAttr:定义角色属性中,会按游戏执行而变化的部分,属于各角色对象自己管理的一部分
- SoldierBuilder、EnemyBuilder:双方阵营橘色的建造者,实际运行中,会调用属性工厂AttrFactory的方法来获取角色属性对象。
- AttrFactory:属性工厂,定义了两个Dictionary容器,用来管理唯一的BaseAttr和EnemyBaseAttr组件
武器属性部分的类结构图
- WeaponAttr:定义武器属性中不会变更可以共享的部分。
- IWeapon:武器类中,声明了一个引用,用来指向共享的武器属性
- AttrFactory:属性工厂,定义了Dictionary容器来管理唯一的WeaponAttr
- WeaponFactory:武器工厂在实际运行时,会调用属性工厂AttrFactory的方法来获取武器属性对象,然后继续产生武器对象的步骤
具体实现
// 角色數值界面
public abstract class ICharacterAttr
{
protected BaseAttr m_BaseAttr = null; // 基本角色數值
//protected int m_MaxHP = 0; // 最高HP值
//protected float m_MoveSpeed = 1.0f; // 移動速度
//protected string m_AttrName = ""; // 數值的名稱
protected int m_NowHP = 0; // 目前HP值
protected IAttrStrategy m_AttrStrategy = null; // 數值的計算策略
public ICharacterAttr()
{
}
// 設定基本屬性
public void SetBaseAttr(BaseAttr BaseAttr)
{
m_BaseAttr = BaseAttr;
}
// 取得基本屬性
public BaseAttr GetBaseAttr()
{
return m_BaseAttr;
}
// 設定數值的計算策略
public void SetAttStrategy(IAttrStrategy theAttrStrategy)
{
m_AttrStrategy = theAttrStrategy;
}
// 取得數值的計算策略
public IAttrStrategy GetAttStrategy()
{
return m_AttrStrategy;
}
// 目前HP
public int GetNowHP()
{
return m_NowHP;
}
// 最大HP
public virtual int GetMaxHP()
{
return m_BaseAttr.GetMaxHP();
}
// 回滿目前HP值
public void FullNowHP()
{
m_NowHP = GetMaxHP();
}
// 移動速度
public virtual float GetMoveSpeed()
{
return m_BaseAttr.GetMoveSpeed();
}
// 取得數值名稱
public virtual string GetAttrName()
{
return m_BaseAttr.GetAttrName();
}
// 初始角色數值
public virtual void InitAttr()
{
m_AttrStrategy.InitAttr(this);
FullNowHP();
}
// 攻擊加乘
public int GetAtkPlusValue()
{
return m_AttrStrategy.GetAtkPlusValue(this);
}
// 取得被武器攻擊後的傷害值
public void CalDmgValue(ICharacter Attacker)
{
// 取得武器功擊力
int AtkValue = Attacker.GetAtkValue();
// 減傷
AtkValue -= m_AttrStrategy.GetDmgDescValue(this);
// 扣去傷害
m_NowHP -= AtkValue;
}
}
// Soldier數值
public class SoldierAttr : ICharacterAttr
{
protected int m_SoldierLv; // Soldier等級
protected int m_AddMaxHP; // 因等級新增的HP值
public SoldierAttr()
{
}
// 設定角色數值
public void SetSoldierAttr(BaseAttr BaseAttr)
{
// 共用元件
base.SetBaseAttr(BaseAttr);
// 外部參數
m_SoldierLv = 1;
m_AddMaxHP = 0;
}
// 設定等級
public void SetSoldierLv(int Lv)
{
m_SoldierLv = Lv;
}
// 取得等級
public int GetSoldierLv()
{
return m_SoldierLv;
}
// 最大HP
public override int GetMaxHP()
{
return base.GetMaxHP() + m_AddMaxHP;
}
// 設定新增的最大生命力
public void AddMaxHP(int AddMaxHP)
{
m_AddMaxHP = AddMaxHP;
}
}
// 產生遊戲用數值界面
public abstract class IAttrFactory
{
// 取得Soldier的數值
public abstract SoldierAttr GetSoldierAttr( int AttrID );
// 取得Soldier的數值:有字首字尾的加乘
public abstract SoldierAttr GetEliteSoldierAttr(ENUM_AttrDecorator emType,int AttrID, SoldierAttr theSoldierAttr);
// 取得Enemy的數值
public abstract EnemyAttr GetEnemyAttr(int AttrID);
// 取得武器的數值
public abstract WeaponAttr GetWeaponAttr(int AttrID);
// 取得加乘用的數值
public abstract AdditionalAttr GetAdditionalAttr( int AttrID );
}
// 實作產生遊戲用數值
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;
private Dictionary<int, AdditionalAttr> m_AdditionalAttrDB = null;
public AttrFactory()
{
InitSoldierAttr();
InitEnemyAttr();
InitWeaponAttr();
InitAdditionalAttr();
}
// 建立所有Soldier的數值
private void InitSoldierAttr()
{
m_SoldierAttrDB = new Dictionary<int, BaseAttr>();
// 基本數值 // 生命力,移動速度,數值名稱
m_SoldierAttrDB.Add(1, new CharacterBaseAttr(10, 3.0f, "新兵"));
m_SoldierAttrDB.Add(2, new CharacterBaseAttr(20, 3.2f, "中士"));
m_SoldierAttrDB.Add(3, new CharacterBaseAttr(30, 3.4f, "上尉"));
}
// 建立所有Enemy的數值
private void InitEnemyAttr()
{
m_EnemyAttrDB = new Dictionary<int, EnemyBaseAttr>();
// 生命力,移動速度,數值名稱,爆擊率,
m_EnemyAttrDB.Add(1, new EnemyBaseAttr(5, 3.0f, "精靈", 10));
m_EnemyAttrDB.Add(2, new EnemyBaseAttr(15, 3.1f, "山妖", 20));
m_EnemyAttrDB.Add(3, new EnemyBaseAttr(20, 3.3f, "怪物", 40));
}
// 建立所有Weapon的數值
private void InitWeaponAttr()
{
m_WeaponAttrDB = new Dictionary<int, WeaponAttr>();
// 攻擊力,距離,數值名稱
m_WeaponAttrDB.Add(1, new WeaponAttr(2, 4, "短槍"));
m_WeaponAttrDB.Add(2, new WeaponAttr(4, 7, "長槍"));
m_WeaponAttrDB.Add(3, new WeaponAttr(8, 10, "火箭筒"));
}
// 建立加乘用的數值
private void InitAdditionalAttr()
{
m_AdditionalAttrDB = new Dictionary<int, AdditionalAttr>();
// 字首產生時隨機產生
m_AdditionalAttrDB.Add(11, new AdditionalAttr(3, 0, "勇士"));
m_AdditionalAttrDB.Add(12, new AdditionalAttr(5, 0, "猛將"));
m_AdditionalAttrDB.Add(13, new AdditionalAttr(10, 0, "英雄"));
// 字尾存活下來即增加
m_AdditionalAttrDB.Add(21, new AdditionalAttr(5, 1, "◇"));
m_AdditionalAttrDB.Add(22, new AdditionalAttr(5, 1, "☆"));
m_AdditionalAttrDB.Add(23, new AdditionalAttr(5, 1, "★"));
}
// 取得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;
}
// 取得加乘過的Soldier角色數值
public override SoldierAttr GetEliteSoldierAttr(ENUM_AttrDecorator emType, int AttrID, SoldierAttr theSoldierAttr)
{
// 取得加乘效果的數值
AdditionalAttr theAdditionalAttr = GetAdditionalAttr(AttrID);
if (theAdditionalAttr == null)
{
Debug.LogWarning("GetEliteSoldierAttr:加乘數值[" + AttrID + "]不存在");
return theSoldierAttr;
}
// 產生裝飾者
BaseAttrDecorator theAttrDecorator = null;
switch (emType)
{
case ENUM_AttrDecorator.Prefix:
theAttrDecorator = new PrefixBaseAttr();
break;
case ENUM_AttrDecorator.Suffix:
theAttrDecorator = new SuffixBaseAttr();
break;
}
if (theAttrDecorator == null)
{
Debug.LogWarning("GetEliteSoldierAttr:無法針對[" + emType + "]產生裝飾者");
return theSoldierAttr;
}
// 設定裝飾對像及加乘數值
theAttrDecorator.SetComponent(theSoldierAttr.GetBaseAttr());
theAttrDecorator.SetAdditionalAttr(theAdditionalAttr);
// 設定新的數值後回傳
theSoldierAttr.SetBaseAttr(theAttrDecorator);
return theSoldierAttr; // 回傳
}
// 取得Enemy的數值,傳入外部參數CritRate
public override EnemyAttr GetEnemyAttr(int AttrID)
{
if (m_EnemyAttrDB.ContainsKey(AttrID) == false)
{
Debug.LogWarning("GetEnemyAttr:AttrID[" + AttrID + "]數值不存在");
return null;
}
// 產生數物件並設定共用的數值資料
EnemyAttr NewAttr = new EnemyAttr();
NewAttr.SetEnemyAttr(m_EnemyAttrDB[AttrID]);
return NewAttr;
}
// 取得武器的數值
public override WeaponAttr GetWeaponAttr(int AttrID)
{
if (m_WeaponAttrDB.ContainsKey(AttrID) == false)
{
Debug.LogWarning("GetWeaponAttr:AttrID[" + AttrID + "]數值不存在");
return null;
}
// 直接回傳共用的武器數值
return m_WeaponAttrDB[AttrID];
}
// 取得加乘用的數值
public override AdditionalAttr GetAdditionalAttr(int AttrID)
{
if (m_AdditionalAttrDB.ContainsKey(AttrID) == false)
{
Debug.LogWarning("GetAdditionalAttr:AttrID[" + AttrID + "]數值不存在");
return null;
}
// 直接回傳加乘用的數值
return m_AdditionalAttrDB[AttrID];
}
}
享元模式的优点
新版运用享元的属性工厂AttrFactor,将属性设置集以更简短的格式呈现,免去了使用 switch case 的一长串语句,方便企划人员阅读和设置。此外,因为共享属性的部分,每一个编号对应的属性对象,在整个游戏执行中只会产生一份,不想就方法那样会产生重复对象而增加内存的负担,对于游戏性能有所提升。
享元模式的应用
在设计游戏中,画面上出现的字典或导弹,大多会使用“对象”的方式来代表。而为了让游戏系统能够有效地产生和管理这些子弹、导弹对象,可以使用享元模式来建立子弹对象池,让其他游戏系统也使用对象池内的子弹,减少因为重复处理产生子弹对象、删除子弹对象所导致的性能损失。