返回

17.装饰模式

动态地附加额外的责任给一个对象。装饰模式提供了一个灵活的选择,让子类可以用来扩展功能。

装饰模式

动态地附加额外的责任给一个对象。装饰模式提供了一个灵活的选择,让子类可以用来扩展功能。

装饰模式的定义

在之前原本的3D绘画工具增加一个新功能,就是能够在原有绘制形状外增加一个“外框”作为标示或编辑提示之用。因此,我们在系统中增加一个称为“额外功能”的类群组,作为后续类似功能的群组。因此这个外框也会使用成像系统,所以实现时与原有的形状群组类似。

参与者说明如下:

  • IShape(形状接口)
    • 定义形状的接口及方法
  • Sphere(形状的实现:球体)
    • 实现胸痛中所需要的形状
  • IShapeDecorator(形状装饰者接口)
    • 定义可以用来装饰形态的接口
    • 增加一个指向被装饰对象的引用成员
    • 需要调用被装饰对象的方法时,可通过引用成员来完成
  • BorderDecorator(形状装饰者的实现:外框装饰者)
    • 实现形状装饰者
    • 在调用“被装饰者的方法”之后或之前,都可以执行本身提供的附加装饰功能,来达到装饰的效果。

具体实现

    // 制訂可被Decorator動態增加權責的物件介面
    public abstract class Component
    {
        public abstract void Operator();
    }

    // 定義可被Decorator動態增加權責的物件
    public class ConcreteComponent : Component
    {
        public override void Operator()
        {
            Debug.Log("ConcreteComponent.Operator");
        }
    }

    // 持有指向Component的reference,須符合Component的介面
    public class Decorator : Component
    {
        Component m_Component;

        public Decorator(Component theComponent)
        {
            m_Component = theComponent;
        }

        public override void Operator()
        {
            m_Component.Operator();
        }
    }

    // 增加額外的權責/功能
    public class ConcreteDecoratorA : Decorator
    {
        Component m_Component;

        public ConcreteDecoratorA(Component theComponent) : base(theComponent)
        {
        }

        public override void Operator()
        {
            base.Operator();
            AddBehaivor();
        }

        private void AddBehaivor()
        {
            Debug.Log("ConcreteDecoratorA.AddBehaivor");
        }
    }

    // 增加額外的權責/功能
    public class ConcreteDecoratorB : Decorator
    {
        Component m_Component;

        public ConcreteDecoratorB(Component theComponent) : base(theComponent)
        {
        }

        public override void Operator()
        {
            AddBehaivor();
            base.Operator();
        }

        private void AddBehaivor()
        {
            Debug.Log("ConcreteDecoratorB.AddBehaivor");
        }
    }

    public class DecoratorTest
    {
        void UnitTest_Shape()
        {
            OpenGL theOpenGL = new OpenGL();

            // 圓型
            Sphere theSphere = new Sphere();
            theSphere.SetRenderEngine(theOpenGL);

            //在圖型加外框
            BorderDecorator theSphereWithBorder = new BorderDecorator(theSphere);
            theSphereWithBorder.SetRenderEngine(theOpenGL);
            theSphereWithBorder.Draw();
        }
    }

前缀后缀功能

需求:

  • 能动态增加玩家角色的角色属性(CharacterAttr)
  • 增加的属性分为两部分:
    • 前缀:当兵营训练完一个角色进入战场时,会出现给这个新角色一个角色属性加成的机会,而新增的角色属性名称,需置于现有属性名称的前方
    • 后缀:当玩家通关一个关卡之后,让所有任在场上存货的ISoldier角色,增加一个勋章数,最多类即三个,而每个勋章书都会对应到一个角色属性作为加成值,而新增的勋章属性名称,需置于现有属性名称的后方
  • 完成的属性名称,需显示在角色信息界面上,让玩家能立即了解当前角色的能力值

参与者说明如下:

  • BaseAttr:定义基本属性接口
  • CharacterBaseAttr:实现角色基本属性
  • BaseAttrDecorator:定义基本属性装饰着接口,类中有一个引用成员,用来指向被装饰的基本属性对象
  • AdditionalAttr:加成用的属性,且有别与角色基本属性的设置及用途
  • PrefixBaseAttr:前缀装饰,会将本省的属性增加在被装饰的基本属性之前,可以实现属性名称显示在前的效果
  • SuffixBaseAttr:后缀装饰者,会将本身的属性增加在被装饰的基本属性之后,可以实现属性名称显示在后的效果

具体实现


// 可以被共用的基本角色數值界面
public abstract class BaseAttr
{
    public abstract int GetMaxHP();
    public abstract float GetMoveSpeed();
    public abstract string GetAttrName();
}

// 實作可以被共用的基本角色數值
public class CharacterBaseAttr : BaseAttr
{
    private int m_MaxHP; // 最高HP值
    private float m_MoveSpeed; // 目前移動速度
    private string m_AttrName; // 數值的名稱	

    public CharacterBaseAttr(int MaxHP, float MoveSpeed, string AttrName)
    {
        m_MaxHP = MaxHP;
        m_MoveSpeed = MoveSpeed;
        m_AttrName = AttrName;
    }

    public override int GetMaxHP()
    {
        return m_MaxHP;
    }

    public override float GetMoveSpeed()
    {
        return m_MoveSpeed;
    }

    public override string GetAttrName()
    {
        return m_AttrName;
    }
}

// 敵方角色的基本數值
public class EnemyBaseAttr : CharacterBaseAttr
{
    public int m_InitCritRate; // 爆擊率

    public EnemyBaseAttr(int MaxHP, float MoveSpeed, string AttrName, int CritRate) : base(MaxHP, MoveSpeed, AttrName)
    {
        m_InitCritRate = CritRate;
    }

    public virtual int GetInitCritRate()
    {
        return m_InitCritRate;
    }
}

// 用於加乘用的數值
public class AdditionalAttr
{
    private int m_Strength; // 力量
    private int m_Agility; // 敏捷
    private string m_Name; // 數值的名稱	

    public AdditionalAttr(int Strength, int Agility, string Name)
    {
        m_Strength = Strength;
        m_Agility = Agility;
        m_Name = Name;
    }

    public int GetStrength()
    {
        return m_Strength;
    }

    public int GetAgility()
    {
        return m_Agility;
    }

    public string GetName()
    {
        return m_Name;
    }
}


// 基本角色數值裝飾者
public abstract class BaseAttrDecorator : BaseAttr
{
    protected BaseAttr m_Component; // 被裝飾對像
    protected AdditionalAttr m_AdditionialAttr; // 代表額外加乘的數值

    // 設定裝飾的目標
    public void SetComponent(BaseAttr theComponent)
    {
        m_Component = theComponent;
    }

    // 設定額外使用的值
    public void SetAdditionalAttr(AdditionalAttr theAdditionalAttr)
    {
        m_AdditionialAttr = theAdditionalAttr;
    }

    public override int GetMaxHP()
    {
        return m_Component.GetMaxHP();
    }

    public override float GetMoveSpeed()
    {
        return m_Component.GetMoveSpeed();
    }

    public override string GetAttrName()
    {
        return m_Component.GetAttrName();
    }
}


// 裝飾類型
public enum ENUM_AttrDecorator
{
    Prefix,
    Suffix,
}

// 字首
public class PrefixBaseAttr : BaseAttrDecorator
{
    public PrefixBaseAttr()
    {
    }

    public override int GetMaxHP()
    {
        return m_AdditionialAttr.GetStrength() + m_Component.GetMaxHP();
    }

    public override float GetMoveSpeed()
    {
        return m_AdditionialAttr.GetAgility() * 0.2f + m_Component.GetMoveSpeed();
    }

    public override string GetAttrName()
    {
        return m_AdditionialAttr.GetName() + m_Component.GetAttrName();
    }
}

// 字尾
public class SuffixBaseAttr : BaseAttrDecorator
{
    public SuffixBaseAttr()
    {
    }

    public override int GetMaxHP()
    {
        return m_Component.GetMaxHP() + m_AdditionialAttr.GetStrength();
    }

    public override float GetMoveSpeed()
    {
        return m_Component.GetMoveSpeed() + m_AdditionialAttr.GetAgility() * 0.2f;
    }

    public override string GetAttrName()
    {
        return m_Component.GetAttrName() + m_AdditionialAttr.GetName();
    }
}

// 直接強化
public class StrengthenBaseAttr : BaseAttrDecorator
{
    protected List<AdditionalAttr> m_AdditionialAttrs; // 多個強化的數值

    public StrengthenBaseAttr()
    {
    }

    public override int GetMaxHP()
    {
        int MaxHP = m_Component.GetMaxHP();
        foreach (AdditionalAttr theAttr in m_AdditionialAttrs)
            MaxHP += theAttr.GetStrength();
        return MaxHP;
    }

    public override float GetMoveSpeed()
    {
        float MoveSpeed = m_Component.GetMoveSpeed();
        foreach (AdditionalAttr theAttr in m_AdditionialAttrs)
            MoveSpeed += theAttr.GetAgility() * 0.2f;
        return MoveSpeed;
    }

    public override string GetAttrName()
    {
        return "直接強化" + m_Component.GetAttrName();
    }
}

// 產生遊戲用數值界面
public abstract class IAttrFactory
{

    ...
    // 取得加乘過的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; // 回傳
    }
    ...
}

// 訓練Soldier
public class TrainSoldierCommand : ITrainCommand
{
    ENUM_Soldier m_emSoldier; // 兵種
    ENUM_Weapon m_emWeapon; // 使用的武器
    int m_Lv; // 等級
    Vector3 m_Position; // 出現位置

    // 訓練
    public TrainSoldierCommand(ENUM_Soldier emSoldier, ENUM_Weapon emWeapon, int Lv, Vector3 Position)
    {
        m_emSoldier = emSoldier;
        m_emWeapon = emWeapon;
        m_Lv = Lv;
        m_Position = Position;
    }

    //  執行
    public override void Execute()
    {
        // 建立Soldier
        ICharacterFactory Factory = PBDFactory.GetCharacterFactory();
        ISoldier Soldier = Factory.CreateSoldier(m_emSoldier, m_emWeapon, m_Lv, m_Position);

        // 依機率產生前輟能力
        int Rate = UnityEngine.Random.Range(0, 100);
        int AttrID = 0;
        if (Rate > 90)
            AttrID = 13;
        else if (Rate > 80)
            AttrID = 12;
        else if (Rate > 60)
            AttrID = 11;
        else
            return;

        // 加上字首能力
        //Debug.Log("加上前輟能力:"+AttrID);
        IAttrFactory AttrFactory = PBDFactory.GetAttrFactory();
        SoldierAttr PreAttr =
            AttrFactory.GetEliteSoldierAttr(ENUM_AttrDecorator.Prefix, AttrID, Soldier.GetSoldierValue());
        Soldier.SetCharacterAttr(PreAttr);
    }
}
// Soldier角色界面
public abstract class ISoldier : ICharacter
{
	protected ENUM_Soldier m_emSoldier = ENUM_Soldier.Null;
	protected int		   m_MedalCount	= 0; 				// 勳章數
	protected const int	   MAX_MEDAL = 3; 					// 最多勳章數 
	protected const int    MEDAL_VALUE_ID = 20;				// 勳章數值起始值

	...
	// 增加勳章
	public virtual void AddMedal()
	{
		if( m_MedalCount >= MAX_MEDAL)
			return ;

		// 增加勳章
		m_MedalCount++;
		// 取得對映的勳章加乘值
		int AttrID =  m_MedalCount + MEDAL_VALUE_ID;

        IAttrFactory theAttrFactory = PBDFactory.GetAttrFactory();

		// 加上字尾能力
		SoldierAttr SufAttr = theAttrFactory.GetEliteSoldierAttr(ENUM_AttrDecorator.Suffix, AttrID, m_Attribute as SoldierAttr);
        SetCharacterAttr(SufAttr);
	}
	...	
}

使用装饰模式的优点

使用装饰模式的方式来新增功能,可避免更改已经实现的程序代码,增加系统的稳定性,也变得更灵活。善用装饰模式的透明性,可以方便组装及加入增加的加成效果。

实现装饰模式时的注意事项

装饰模式适合项目后期增加系统功能时使用

对于项目进入后期或项目已上市的维护周期来说,使用装饰模式来增加现有系统的附加功能确实是较稳定的方式。但落实项目在早期就已经规划要实现前缀后缀功能,那么可以将这种“附加于对象上的功能”列于早期的开发设计中,否则果冻套附加功能,会造成调试山的困难,也会让后续维护者不容易看懂原始设计者最初的组装顺序。

早期规划时可以将附加功能加入设计之中

如果系统已预期某项功能会以大量的附加组件来扩展功能的话,那么或许可以采用Unity3D引擎中的“游戏对象”和“组件”的设计方式:游戏对象只是一个能在三位空间表示位置的类,但这个类可以利用不断往上增加组件的方式来强化其功能。处理具备动态增加、删除组件的灵活性外,通过Unity3D界面查看组件列表中的类,也能轻易看出这个游戏对象具备了什么样的功能,提高了系统的维护性同时减少了调试的难度。

装饰模式的应用

  • 网络在线游戏中数据封包的加密解密,也是许多介绍设计模式书中会提到的范例。通过额外附加的“信息加密装饰者”,就可以让原本传递的信息增加安全性,而且可以实现不同的加密方式来层层包裹,而修改的过程中,都不会影响到原有的数据封包的传送架构。
  • 界面特效,有时候游戏界面中会特别提示玩家,某个事件发生了或是提醒玩家有个奖励可以领取,对于这类需求,可以在原有的界面组件上增加一个“接口特效装饰者”,而这样的实现方式会比较灵活,修改也比较方便。