装饰模式
动态地附加额外的责任给一个对象。装饰模式提供了一个灵活的选择,让子类可以用来扩展功能。
装饰模式的定义
在之前原本的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界面查看组件列表中的类,也能轻易看出这个游戏对象具备了什么样的功能,提高了系统的维护性同时减少了调试的难度。
装饰模式的应用
- 网络在线游戏中数据封包的加密解密,也是许多介绍设计模式书中会提到的范例。通过额外附加的“信息加密装饰者”,就可以让原本传递的信息增加安全性,而且可以实现不同的加密方式来层层包裹,而修改的过程中,都不会影响到原有的数据封包的传送架构。
- 界面特效,有时候游戏界面中会特别提示玩家,某个事件发生了或是提醒玩家有个奖励可以领取,对于这类需求,可以在原有的界面组件上增加一个“接口特效装饰者”,而这样的实现方式会比较灵活,修改也比较方便。