返回

10.享元模式

使用共享的方式,让一大群小规模对象能更有效的运行。

享元模式

使用共享的方式,让一大群小规模对象能更有效的运行。

享元模式的定义

定义的两个重点:“共享”与“一大群小规模对象”

首先,“一大群小规模对象”指的是:虽然有时候类的组成很简单,可能只有几个类型为 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 的一长串语句,方便企划人员阅读和设置。此外,因为共享属性的部分,每一个编号对应的属性对象,在整个游戏执行中只会产生一份,不想就方法那样会产生重复对象而增加内存的负担,对于游戏性能有所提升。

享元模式的应用

在设计游戏中,画面上出现的字典或导弹,大多会使用“对象”的方式来代表。而为了让游戏系统能够有效地产生和管理这些子弹、导弹对象,可以使用享元模式来建立子弹对象池,让其他游戏系统也使用对象池内的子弹,减少因为重复处理产生子弹对象、删除子弹对象所导致的性能损失。