1.前言
本文介绍的桥接模式,在正式开始桥接模式之前我们先来看一个案例,我们都知道汽车品牌有很多种,比如常见的BBA,每一个品牌也都有各种档位类型,例如自动挡、手动挡、手自一体等,这样的例子还有很多,例如电脑品牌和操作系统的关系,在游戏中常用的桥接模式典型的有武器跟角色的关系,下面会具体举例子讲解。
基本介绍
桥接模式
桥接模式(Bridge Pattern):桥接模式也有的书叫它桥梁模式,英文是Bridge Design Pattern。在Gof的《设计模式》中,是这么定义的:”Decouple an abstraction from its implementation so that the two can vary independently”。翻译成中文就是:”将抽象和实现解耦,让它们可以独立变化”。它是一种结构性设计模式,桥接模式是基于类的最小设计原则以及组合大于继承。桥接模式的用意是将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化。
UML
案例实战
需求
我们在FPS类游戏中会碰到这样的需求——实现武器和角色,无论是敌人还是我方角色都能通过不同的武器击杀对方,武器有手枪、散弹枪、以及火箭炮,并以”攻击力”和”攻击距离”来区分他们的威力。我们可能会这样实现:
上面简单介绍了下桥接模式,下面我们还是通过以上例子来理解分析,”桥接”顾名思义就是这一组跟那一组两组对象通过某种中间关系链接在一起。”当两个群组因为功能上的需求,想要进行链接合作,但又希望两组类可以自行发展互相不受对方变化而影响”,上面角色跟武器的实现案例,武器和角色分别是两组群组,其实上面的实现只是考虑了角色通过子类通过基类继承来实现,然后传入武器对象,这中实现思路有点上一讲介绍的控制反转的味道,那既然角色可以这样设计,为何不把武器也这样设计呢,然后两个群组之间就通过两个群组的接口来实现,就好比两家结婚,双方都有一个媒人来传递信息,这是我对桥接模式的理解,哈哈。基于这种想法,我们来改造一下角色和武器的实现。
说明:
- ICharacter:角色的抽象接口拥有一个IWeapon的对象引用,并在接口中申明了一个武器攻击目标WeaponAttackTarget()方法让子类可以调用,同时要求继承子类必须在Attack()中重新实现攻击目标的功能。
- ISoldier、IEnemy:双方阵容单位实现攻击目标Attack()时,只需要调用父类的WeaponAttackTarget方法就可以使用当前武器攻击对手。
- IWeapon:武器接口,定义游戏中对于武器的操作和使用方法。
- WeaponGun、WeaponRifle、WeaponRocket:游戏中可以使用三中武器对象。
下面就是我们的初步设计代码。
- 角色基类
1 | public abstract class ICharacter |
- 武器类
1 | using UnityEngine; |
- 敌人使用武器
1 | using System.Collections; |
- 玩家使用武器
1 | using System.Collections; |
初步分析
以上实现存在明显两个缺点:
- 每当添加一个角色时,继承自ICharacter接口的角色重新定义Attack都必须针对不同的武器进行判断,并且要写重复的相同的代码。
- 每当新增武器时,所有角色的Attack都需要重新改造,这样增加维护成本。
为了解决以上问题,下面我们桥接模式隆重登场了!
桥接模式实现武器和角色功能
- 武器接口
1 | using System.Collections; |
- 手枪武器的实现
1 | public class WeaponGun:IWeapon |
其他武器同理…
- 角色接口
1 | using System.Collections; |
- 桥接模式角色实现
1 | //角色接口 |
桥接模式改造后分析
以上设计运用桥接模式后的ICharacter就是群组”抽象类”,它定义了”攻击目标”功能,但实现攻击目标功能的却是群组”IWeapon武器类”,对于ICharacter以及其继承都不会理会IWeapon群组的变化,尤其在新增武器类的时候也不会影响角色类。对于ICharacter来说,它面对的指示IWeapon这个接口类,这让两个群组耦合度降到最低。
桥接模式跟适配器模式区别
这两种模式都是为了让两个东西配合工作,适配器模式是改变已有的接口让其配合工作,把目的相似、接口不同的类适配器来。桥接模式是分离抽象和具体实现,目的是分离,把类的抽象和具体实现分离开来,在此基础上把接口结合起来。
桥接模式跟组合模式区别
桥接模式是平行级别上类的组合,而组合模式是强调部分和整体的组合。
思考
结合以上案例,可以思考另外一个需求:用不同的图形渲染引擎如OpenGL、DirectX来绘制不同的图形。
提示:渲染引擎RenderEngine和图形属于两大群体,这两大群体都要单独有各自的“抽象类”抽象类RenderEngine和抽象类Shape。
- 抽象类RenderEngine肯定要有一个抽象功能方法就是Draw(string shape),这个是被子类具体的渲染引擎重写,因为各自的引擎都有自己独特的渲染方法,所以这个Draw方法就被重写调用各个引擎自己的渲染方法GLRender()和DXRender()。
- 抽象类Shape肯定也要有一个保护对象RenderEngine和设置这个对象的注入方法SetRenderEngine(RenderEngine engine)以及抽象方法Draw()。Draw方法是要被子类重写实现:调用renderEngine的Draw方法来绘制自己。