1.前言

继上一讲IOC模式的基础上继续本讲桥接模式,笔者感觉桥接模式是23种设计模式中桥接模式是最好用但也是最难理解的设计模式之一,23中设计模式就好武侠剧中一本武功秘籍,我们在工作过程中想要熟练运用其中的每一种设计模式就好比跟高手过招想要能运用好武侠秘籍中的每一招每一式,并且能随着对手出招的不同我们能随机应变对应的招数,这就要求我们对每一种设计模式都理解的非常深刻才能运用自如,打出组合拳的效果。

2.需求

我们在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 = ,
Gun,
Rifle,
Rocket,
} public class Weapon
{
protected ENUM_Weapon m_EmEwapon = ENUM_Weapon.Null;
protected int m_AtkValue = ;//攻击力
protected int m_AtkRange = ;//攻击距离
protected int m_AtkPlusValue = ;//额外加成 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 = ;
switch(m_Weapon.GetWeaponType())
{
case ENUM_Weapon.Gun:
//显示武器特效
m_Weapon.ShowBulletEffect(target.GetPosition(), 0.3f, 0.2f);
m_Weapon.ShowSoundEffect("GunShot");
atkPlusValue = GetAtkPluginValue(, );
break;
case ENUM_Weapon.Rifle:
m_Weapon.ShowBulletEffect(target.GetPosition(), 0.4f, 0.2f);
m_Weapon.ShowSoundEffect("GunShot");
atkPlusValue = GetAtkPluginValue(, );
break;
case ENUM_Weapon.Rocket:
m_Weapon.ShowBulletEffect(target.GetPosition(), 0.5f, 0.2f);
m_Weapon.ShowSoundEffect("GunShot");
atkPlusValue = GetAtkPluginValue(, );
break;
}
m_Weapon.SetAtkPlusValue(atkPlusValue);
m_Weapon.Fire(target);
}
private int GetAtkPlusValue(int rate, int atkValue)
{
int randValue = UnityEngine.Random.Range(, );
if (rate > randValue)
return atkValue;
return ;
}
}
  • 玩家使用武器
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);
}
}

3.初步分析

以上实现存在明显两个缺点:

  • 每当添加一个角色时,继承自ICharacter接口的角色重新定义Attack都必须针对不同的武器进行判断,并且要写重复的相同的代码。
  • 每当新增武器时,所有角色的Attack都需要重新改造,这样增加维护成本。 
    为了解决以上问题,下面我们桥接模式隆重登场了!

4.桥接模式

桥接模式(Bridge Pattern):桥接模式的用意是将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化。 
以上是桥接模式的官方定义,我们还是通过以上例子来理解分析,”桥接”顾名思义就是这一组跟那一组两组对象通过某种中间关系链接在一起。”当两个群组因为功能上的需求,想要进行链接合作,但又希望两组类可以自行发展互相不受对方变化而影响”,上面角色跟武器的实现案例,武器和角色分别是两组群组,其实上面的实现只是考虑了角色通过子类通过基类继承来实现,然后传入武器对象,这中实现思路有点上一讲介绍的控制反转的味道,那既然角色可以这样设计,为何不把武器也这样设计呢,然后两个群组之间就通过两个群组的接口来实现,就好比两家结婚,双方都有一个媒人来传递信息,这是我对桥接模式的理解,哈哈。基于这种想法,我们来改造一下角色和武器的实现。 

说明:

  • ICharacter:角色的抽象接口拥有一个IWeapon的对象引用,并在接口中申明了一个武器攻击目标WeaponAttackTarget()方法让子类可以调用,同时要求继承子类必须在Attack()中重新实现攻击目标的功能。
  • ISoldier、IEnemy:双方阵容单位实现攻击目标Attack()时,只需要调用父类的WeaponAttackTarget方法就可以使用当前武器攻击对手。
  • IWeapon:武器接口,定义游戏中对于武器的操作和使用方法。
  • WeaponGun、WeaponRifle、WeaponRocket:游戏中可以使用三中武器对象。

5.桥接模式实现武器和角色功能

  • 武器接口
using System.Collections;
using System.Collections.Generic;
using UnityEngine; public abstract class IWeapon
{
//属性
protected int m_AtkPlusValue = ;
protected int m_Atk = ;
protected float m_Range = ; protected GameObject m_GameObject = null;
protected ICharacter m_WeaponOwner = null;//武器拥有者 //发射特效
protected float m_EffectDisplayTime = ;
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)
{
...
}
}

4.桥接模式改造后分析

以上设计运用桥接模式后的ICharacter就是群组”抽象类”,它定义了”攻击目标”功能,但实现攻击目标功能的却是群组”IWeapon武器类”,对于ICharacter以及其继承都不会理会IWeapon群组的变化,尤其在新增武器类的时候也不会影响角色类。对于ICharacter来说,它面对的指示IWeapon这个接口类,这让两个群组耦合度降到最低。

5.思考

结合以上案例,可以思考另外一个需求:用不同的图形渲染引擎如OpenGL、DirectX来绘制不同的图形。 
提示:渲染引擎RenderEngine和图形属于两大群体,这两大群体都要单独有各自的“抽象类”抽象类RenderEngine和抽象类Shape。

    • 抽象类RenderEngine肯定要有一个抽象功能方法就是Draw(string shape),这个是被子类具体的渲染引擎重写,因为各自的引擎都有自己独特的渲染方法,所以这个Draw方法就被重写调用各个引擎自己的渲染方法GLRender()和DXRender()。
    • 抽象类Shape肯定也要有一个保护对象RenderEngine和设置这个对象的注入方法SetRenderEngine(RenderEngine engine)以及抽象方法Draw()。Draw方法是要被子类重写实现:调用renderEngine的Draw方法来绘制自己。

[Unity 设计模式]桥接模式(BridgePattern)的更多相关文章

  1. 转:设计模式-----桥接模式(Bridge Pattern)

    转自:http://www.cnblogs.com/houleixx/archive/2008/02/23/1078877.html 记得看原始链接的评论. 学习设计模式也有一段时间了,今天就把我整理 ...

  2. 跟着ZHONGHuan学习设计模式--桥接模式

    转载请注明出处! ! !http://blog.csdn.net/zhonghuan1992 全部配套代码均在github上:https://github.com/ZHONGHuanGit/Desig ...

  3. linkin大话设计模式--桥接模式

    linkin大话设计模式--桥接模式 桥接模式是一种结构化模式,他主要应对的是:由于实际的需要,某个类具有2个或者2个以上维度的变化,如果只是使用继承将无法实现功能,或者会使得设计变得相当的臃肿.我们 ...

  4. java设计模式——桥接模式

    一. 定义与类型 定义:将抽象部分与他的具体实现部分分离,使它们都可以独立的变化,通过组合的方式建立两个类之间的联系,而不是继承 类型:结构性. 二. 使用场景 (1) 抽象和具体实现之间增加更多的灵 ...

  5. 【设计模式】Java设计模式 - 桥接模式

    [设计模式]Java设计模式 - 桥接模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 原创作品,更多关注我CSDN: 一个有梦有戏的人 准备将博客园.CSDN一起 ...

  6. JAVA 设计模式 桥接模式

    用途 桥接模式 (Bridge) 将抽象部分与实现部分分离,使它们都可以独立的变化. 桥接模式是一种结构式模式. 结构

  7. 深入浅出设计模式——桥接模式(Bridge Pattern)

    模式动机设想如果要绘制矩形.圆形.椭圆.正方形,我们至少需要4个形状类,但是如果绘制的图形需要具有不同的颜色,如红色.绿色.蓝色等,此时至少有如下两种设计方案: 第一种设计方案是为每一种形状都提供一套 ...

  8. javascript设计模式-桥接模式

    在系统中,某些类由于自身逻辑,具有两个或两个以上维度的变化,如何使得该类型可以沿多个方向变化,但又不引入额外的复杂度,这就是桥接模式要解决的问题. 定义:桥接模式(Bridge),将抽象部分与它的实现 ...

  9. 设计模式 -- 桥接模式(Bridge Pattern)

    桥接模式 Bridge Pattern 结构设计模式 定义: 分离抽象部分和实现部分,使他们独立运行. 避免使用继承导致系统类个数暴增,可以考虑桥接模式. 桥接模式将继承关系转化为关联关系,减少耦合, ...

随机推荐

  1. 20. leetcode 171. Excel Sheet Column Number

    Given a column title as appear in an Excel sheet, return its corresponding column number. For exampl ...

  2. table表格中单击添加动态编辑框

    var $newNode=$("<input type='text' style='width:250px; height:20px; maxlength='20' id='texti ...

  3. 实现wpf的值转换器

    从数据库取出来的数据是1,2,3,4,5,不过要显示在控件上的,是1,2,3,4,5对应的string值,怎么办?wpf提供了很好的实现方法,那就是值转换器,我们需要做的是: 1.定义值转换类,继承I ...

  4. ansible批量分发免密钥登陆python脚本

    最近看了看强大的号称自动化运维的三大利器之一的--ansible,ok,亲测之后,确实感觉,对于我们这种DBA工作者来说,确实很受益. 值得注意的是ansible要求被管理服务器python版本不低于 ...

  5. centos7安装mysql(yum)

    centos7安装mysql(yum) ----安装环境----依赖安装----检查mysql是否已安装----安装----验证是否添加成功----选择要启用的mysql版本----通过Yum安装my ...

  6. EF对于已有数据库的Code First支持

    EF对于已有数据库的Code First支持 原文链接 本文将逐步介绍怎样用Code First的方式基于已有数据库进行开发.Code First支持你使用C#或者VB.Net定义类.并使用数据模型标 ...

  7. Oracle高水位2

    --Oracle高水位2---------------------2013/11/24 一.什么是水线(High Water Mark)? 所有的oracle段(segments,在此,为了理解方便, ...

  8. Android App 压力测试 monkeyrunner

    Android App 压力测试 第一部分 背景 1. 为什么要开展压力测试? 2. 什么时候开展压力测试?第二部分 理论 1. 手工测试场景 2. 自动测试创建 3. Monkey工具 4. ADB ...

  9. 玩转JS系列之代码加载篇

    从前我们这样写js <script type="text/javascript"> function a(){ console.log('a init');}funct ...

  10. How to enable your website to public(set up your web server at home)

    As a so exciting dream, I would like set up my owned web site which can be accessed anywhere nomatte ...