继上一讲IOC模式的基础上继续本讲桥接模式,笔者感觉桥接模式是23种设计模式中桥接模式是最好用但也是最难理解的设计模式之一,23中设计模式就好武侠剧中一本武功秘籍,我们在工作过程中想要熟练运用其中的每一种设计模式就好比跟高手过招想要能运用好武侠秘籍中的每一招每一式,并且能随着对手出招的不同我们能随机应变对应的招数,这就要求我们对每一种设计模式都理解的非常深刻才能运用自如,打出组合拳的效果。
我们在FPS类游戏中会碰到这样的需求——实现武器和角色,无论是敌人还是我方角色都能通过不同的武器击杀对方,武器有手枪、散弹枪、以及火箭炮,并以”攻击力”和”攻击距离”来区分他们的威力。我们可能会这样实现:
上面是我们的一个UML示例图,这里笔者很惭愧没用过UML专业的绘图工具,就临时用绘图软件简单的画了一下。下面就是我们的初步设计代码。
public abstract class ICharacter
{
//拥有一把武器
protected Weapon m_Weapon = null;
//攻击目标
public abstract void Attack(ICharacter target);
}
using UnityEngine;
public enum ENUM_Weapon
{
Null = 0,
Gun,
Rifle,
Rocket,
}
public class Weapon
{
protected ENUM_Weapon m_EmEwapon = ENUM_Weapon.Null;
protected int m_AtkValue = 0;//攻击力
protected int m_AtkRange = 0;//攻击距离
protected int m_AtkPlusValue = 0;//额外加成
public Weapon(ENUM_Weapon type, int atkValue, int atkRange)
{
m_EmEwapon = type;
m_AtkValue = atkValue;
m_AtkRange = atkRange;
}
public ENUM_Weapon GetWeaponType()
{
return m_EmEwapon;
}
public void Fire(ICharacter target)
{
//
}
public void SetAtkPlusValue(int atkPlusValue)
{
m_AtkPlusValue = atkPlusValue;
}
public void ShowBulletEffect(Vector3 targetPosition, float lineWidth, float displayTime)
{
}
public void ShowShootEffect()
{
}
public void ShowSoundEffect(string clipName)
{
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class IEnemy : ICharacter
{
public IEnemy()
{ }
public override void Attack(ICharacter target)
{
m_Weapon.ShowShootEffect();
int atkPlusValue = 0;
switch(m_Weapon.GetWeaponType())
{
case ENUM_Weapon.Gun:
//显示武器特效
m_Weapon.ShowBulletEffect(target.GetPosition(), 0.3f, 0.2f);
m_Weapon.ShowSoundEffect("GunShot");
atkPlusValue = GetAtkPluginValue(5, 20);
break;
case ENUM_Weapon.Rifle:
m_Weapon.ShowBulletEffect(target.GetPosition(), 0.4f, 0.2f);
m_Weapon.ShowSoundEffect("GunShot");
atkPlusValue = GetAtkPluginValue(5, 20);
break;
case ENUM_Weapon.Rocket:
m_Weapon.ShowBulletEffect(target.GetPosition(), 0.5f, 0.2f);
m_Weapon.ShowSoundEffect("GunShot");
atkPlusValue = GetAtkPluginValue(5, 20);
break;
}
m_Weapon.SetAtkPlusValue(atkPlusValue);
m_Weapon.Fire(target);
}
private int GetAtkPlusValue(int rate, int atkValue)
{
int randValue = UnityEngine.Random.Range(0, 100);
if (rate > randValue)
return atkValue;
return 0;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ISoldier : ICharacter
{
public ISoldier()
{
}
public override void Attack(ICharacter target)
{
m_Weapon.ShowShootEffect();
switch(m_Weapon.GetWeaponType())
{
case ENUM_Weapon.Gun:
m_Weapon.ShowBulletEffect(target.GetPosition(), 0.03f, 0.2f);
m_Weapon.ShowSoundEffect("GunShot");
break;
case ENUM_Weapon.Rifle:
m_Weapon.ShowBulletEffect(target.GetPosition(), 0.5f, 0.2f);
m_Weapon.ShowSoundEffect("RifleShot");
break;
case ENUM_Weapon.Rocket:
m_Weapon.ShowBulletEffect(target.GetPosition(), 0.8f, 0.2f);
m_Weapon.ShowSoundEffect("RocketShot");
break;
}
m_Weapon.Fire(target);
}
}
以上实现存在明显两个缺点:
桥接模式(Bridge Pattern):桥接模式的用意是将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化。
以上是桥接模式的官方定义,我们还是通过以上例子来理解分析,”桥接”顾名思义就是这一组跟那一组两组对象通过某种中间关系链接在一起。”当两个群组因为功能上的需求,想要进行链接合作,但又希望两组类可以自行发展互相不受对方变化而影响”,上面角色跟武器的实现案例,武器和角色分别是两组群组,其实上面的实现只是考虑了角色通过子类通过基类继承来实现,然后传入武器对象,这中实现思路有点上一讲介绍的控制反转的味道,那既然角色可以这样设计,为何不把武器也这样设计呢,然后两个群组之间就通过两个群组的接口来实现,就好比两家结婚,双方都有一个媒人来传递信息,这是我对桥接模式的理解,哈哈。基于这种想法,我们来改造一下角色和武器的实现。
说明:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class IWeapon
{
//属性
protected int m_AtkPlusValue = 0;
protected int m_Atk = 0;
protected float m_Range = 0;
protected GameObject m_GameObject = null;
protected ICharacter m_WeaponOwner = null;//武器拥有者
//发射特效
protected float m_EffectDisplayTime = 0;
protected ParticleSystem m_Particles;
protected AudioSource m_Audio;
//显示子弹特效
protected void ShowBulletEffect(Vector3 targetPosition, float disPlayTime)
{
m_EffectDisplayTime = disPlayTime;
}
//显示枪口特效
protected void ShowShootEffect()
{
if(m_Particles != null)
{
m_Particles.Stop();
m_Particles.Play();
}
}
//显示音效
protected void ShowSoundEffect(string clipName)
{
if (m_Audio == null)
return;
IAssetFactory factory = Factory.GetAssetFactory();
var clip = factory.LoadAudioClip(clipName);
if (clip == null)
return;
m_Audio.clip = clip;
m_Audio.Play();
}
//攻击目标
public abstract void Fire(ICharacter target);
}
public class WeaponGun:IWeapon
{
public WeaponGun()
{
}
public override void Fire(ICharacter target)
{
ShowShootEffect();
ShowBulletEffect(target.GetPosition(), 0.3f, 0.2f);
ShowSoundEffect("GunShot");
target.UnderAttack(m_WeaponOwner);
}
}
其他武器同理…
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class ICharacter
{
protected Weapon m_Weapon = null;
public void SetWeapon(IWeapon weapon)
{
if (m_Weapon != null)
m_Weapon.Release();
m_Weapon = weapon;
//设置武器拥有者
m_Weapon.SetOwener(this);
}
//获取武器
public IWeapon GetWeapon()
{
return m_Weapon;
}
protected void SetWeaponAtkPlusValue(int value)
{
m_Weapon.SetAtkPlusValue(value);
}
protected void WeaponAttackTarget(ICharacter target)
{
m_Weapon.Fire(target);
}
//获取武器攻击力
public void GetAtkValue()
{
return m_Weapon.GetAtkValue();
}
/// <summary>
/// 获得攻击距离
/// </summary>
/// <returns></returns>
public float GetAttackRange()
{
return m_Weapon.GetAtkRange();
}
/// <summary>
/// 攻击目标
/// </summary>
/// <param name="target"></param>
public abstract void Attack(ICharacter target);
/// <summary>
/// 被其他角色攻击
/// </summary>
/// <param name="attacker"></param>
public abstract void UnderAttack(ICharacter attacker);
}
//角色接口
public class ISolider : ICharacter
{
public override void Attack(ICharacter target)
{
WeaponAttackTarget(target);
}
public override void UnderAttack(ICharacter attacker)
{
...
}
}
//Enemy接口
public class IEnemy:ICharacter
{
public override void Attack(ICharacter target)
{
SetWeaponAtkPlusValue(m_Weapon.GetAtkPlusValue);
WeaponAttackTarget(target);
}
public override void UnderAttack(ICharacter attacker)
{
...
}
}
以上设计运用桥接模式后的ICharacter就是群组”抽象类”,它定义了”攻击目标”功能,但实现攻击目标功能的却是群组”IWeapon武器类”,对于ICharacter以及其继承都不会理会IWeapon群组的变化,尤其在新增武器类的时候也不会影响角色类。对于ICharacter来说,它面对的指示IWeapon这个接口类,这让两个群组耦合度降到最低。
结合以上案例,可以思考另外一个需求:用不同的图形渲染引擎如OpenGL、DirectX来绘制不同的图形。
提示:渲染引擎RenderEngine和图形属于两大群体,这两大群体都要单独有各自的“抽象类”抽象类RenderEngine和抽象类Shape。
==================== 迂者 Aladdin CSDN博客专栏=================
MyBlog:http://blog.csdn.net/dingxiaowei2013
Unity QQ群:159875734
====================== 相互学习,共同进步 ===================