一、动机(Motivation)

在很多游戏场景中,会有这样的情况:【装备】本身会有的自己固有的逻辑,比如枪支,会有型号的问题,同时现在很多的游戏又在不同的介质平台上运行和使用,这样就使得游戏的【装备】具有了两个变化的维度——一个变化的维度为“平台的变化”,另一个变化的维度为“型号的变化”。如果我们要写代码实现这款游戏,难道我们针对每种平台都实现一套独立的【装备】吗?复用在哪里?如何应对这种“多维度的变化”?如何利用面向对象技术来使得【装备】可以轻松地沿着“平台”和“型号”两个方向变化,而不引入额外的复杂度?

二、意图(Intent)

将抽象部分与实现部分分离,使它们都可以独立地变化。

桥模式不能只是认为是抽象和实现的分离,它其实并不仅限于此。其实两个都是抽象的部分,更确切的理解,应该是将一个事物中多个维度的变化分离。

三、结构(Structure)

其中imp的地方就是一个组合。Abstraction就是我们例子中的Tank,它的子类RefinedAbstraction就是T50等型号。Implementor是TankPlatformImplementation类,ConcreteImplementorA和ConcreteImplementorB分别是PCTankImplementation和MobileTankImplementation。

整个设计模式的关键就是组合的使用。

四、模式的组成

桥接模式的结构包括Abstraction、RefinedAbstraction、Implementor、ConcreteImplementorA和ConcreteImplementorB五个部分,其中:
(1)、抽象化角色(Abstraction):抽象化给出的定义,并保存一个对实现化对象(Implementor)的引用。
(2)、修正抽象化角色(Refined Abstraction):扩展抽象化角色,改变和修正父类对抽象化的定义。
(3)、实现化角色(Implementor):这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。
(4)、具体实现化角色(Concrete Implementor):这个角色给出实现化角色接口的具体实现。
  在桥接模式中,两个类Abstraction和Implementor分别定义了抽象与行为类型的接口,通过调用两接口的子类实现抽象与行为的动态组合。

五、桥接模式的具体代码实现

假如我们需要开发一个同时支持PC和手机端的坦克游戏,游戏在PC和手机上功能都一样,都有同样的类型,面临同样的功能需求变化,比如坦克可能有很多种不同的型号:T50,T75,T90……

对于其中的坦克设计,我们可能很容易设计出来一个Tank的抽象基类,然后各种不同型号的Tank继承自该类;

这一步实现一点问题也没有,也符合开闭原则,继续往下看。

另外的变化原因

但是PC和手机上的图形绘制、声效、操作等实现完全不同……因此对于各种型号的坦克,都要提供各种不同平台上的坦克实现:

我们一般会设计成这样,但是这样看很怪,这样的设计会带来很多问题:有很多重复代码,类的结构过于复杂,难以维护,最致命的是引入任何新平台,比如在TV上的Tank游戏,都会让整个类层级结构复杂化。我们做软件,修改的时候,修改的越少越好,说明隔离的比较好。

public abstract class TankModel
{
protected TankPlatformImplementation _tankImp; public TankModel(TankPlatformImplementation tankImp)
{
_tankImp = tankImp;
}
public abstract void Run();
} public abstract class TankPlatformImplementation
{
public abstract void MoveTankTo(int x, int y);
public abstract void DrawTank();
public abstract void Attack();
} /// <summary>
/// PC坦克
/// </summary>
public class PCTankImplatation : TankPlatformImplementation
{
string _tankModel;
public PCTankImplatation(string tankModel)
{
_tankModel = tankModel;
}
/// <summary>
/// 绘制坦克
/// </summary>
public override void DrawTank()
{
Console.WriteLine(_tankModel + "PC坦克绘制成功!");
}
/// <summary>
/// 坦克移动
/// </summary>
/// <param name="x">x坐标</param>
/// <param name="y">y坐标</param>
public override void MoveTankTo(int x, int y)
{
Console.WriteLine(_tankModel + "PC坦克已经移动到了坐标(" + x + "," + y + ")处");
}
/// <summary>
/// 攻击
/// </summary>
public override void Attack()
{
Console.WriteLine(_tankModel + "PC坦克开始攻击");
}
}
/// <summary>
/// T50型号坦克
/// </summary>
public class T50 : TankModel
{
public T50(TankPlatformImplementation tankImp) : base(tankImp) { }
public override void Run()
{
_tankImp.DrawTank();
_tankImp.MoveTankTo(100, 100);
_tankImp.Attack();
}
} /// <summary>
/// 客户端调用
/// </summary>
public class App
{
void Main(string[] agrs)
{
T50 t = new T50(new PCTankImplatation("T50"));
t.Run();
}
}

使用了桥接模式后,当需求发生变化后就很容易来应对了,假如现在又多了一种T60型号的坦克,并且添加了一个手机平台。只需要添加T60型号的具体类和手机平台具体类即可,如下:

/// <summary>
/// 手机坦克
/// </summary>
public class MobileTankImplatation : TankPlatformImplementation
{
string _tankModel;
public MobileTankImplatation(string tankModel)
{
_tankModel = tankModel;
}
/// <summary>
/// 绘制坦克
/// </summary>
public override void DrawTank()
{
Console.WriteLine(_tankModel+"Mobile坦克绘制成功!");
}
/// <summary>
/// 坦克移动
/// </summary>
/// <param name="x">x坐标</param>
/// <param name="y">y坐标</param>
public override void MoveTankTo(int x, int y)
{
Console.WriteLine(_tankModel+"Mobile坦克已经移动到了坐标(" + x + "," + y + ")处");
}
/// <summary>
/// 攻击
/// </summary>
public override void Attack()
{
Console.WriteLine(_tankModel+"Mobile坦克开始攻击");
}
}
/// <summary>
/// T60型号坦克
/// </summary>
public class T60 : TankModel
{
public T60(TankPlatformImplementation tankImp) : base(tankImp) { }
public override void Run()
{
_tankImp.DrawTank();
_tankImp.MoveTankTo(400, 100);
_tankImp.Attack();
}
}

添加这两个类后现在我们有T50型号、 T60型号 、PC平台、手机平台,虽然只添加了两个类,但现在有了四种组合,看客户端代码的调用:

/// <summary>
/// 客户端调用
/// </summary>
public class App
{
void Main(string[] agrs)
{
//T50在PC上
T50 t50PC = new T50(new PCTankImplatation("T50"));
t50PC.Run();
//T50在Mobile上
T50 t50Mobile = new T50(new MobileTankImplatation("T50"));
t50Mobile.Run();
//T60在PC上
T60 t60PC = new T60(new PCTankImplatation("T60"));
t60PC.Run();
//T60在Mobile上
T60 t60Mobile = new T60(new MobileTankImplatation("T60"));
t60Mobile.Run();
}
}

六、桥接模式的实现要点:

1.Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。
    2.所谓抽象和实现沿着各自维度的变化,即“子类化”它们,得到各个子类之后,便可以任意组合它们,从而获得不同平台上的不同型号。
    3.Bridge模式有时候类似于多继承方案,但是多继承方案往往违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差。Bridge模式是比多继承方案更好的解决方法。
    4.Bridge模式的应用一般在“两个非常强的变化维度”,有时候即使有两个变化的维度,但是某个方向的变化维度并不剧烈——换言之两个变化不会导致纵横交错的结果,并不一定要使用Bridge模式。

1、桥接模式的优点:

(1)、把抽象接口与其实现解耦。
          (2)、抽象和实现可以独立扩展,不会影响到对方。
          (3)、实现细节对客户透明,对用于隐藏了具体实现细节。

2、桥接模式的缺点:

(1)、增加了系统的复杂度

3、桥接模式的使用场景:

(1)、如果一个系统需要在构件的抽象化角色和具体化角色之间添加更多的灵活性,避免在两个层次之间建立静态的联系。
         (2)、设计要求实现化角色的任何改变不应当影响客户端,或者实现化角色的改变对客户端是完全透明的。
         (3)、需要跨越多个平台的图形和窗口系统上。
         (4)、 一个类存在两个独立变化的维度,且两个维度都需要进行扩展。

下面是针对上面的例子,多继承接口的一种写法:

这样PCT50既需要写T50的实现,又要写Platform的实现,它把型号和平台的变化都引入了PCT50。这样就把两个本不该扭在一起的事务扭在了一起,这样的设计更加糟糕,而且也违背了类的单一职责原则。

Bridge模式的应用一般在“两个非常强的变化维度”,有时候即使有两个变化的维度,但是某个方向的变化维度并不剧烈——换言之两个变化不会导致纵横交错的结果,并不一定要使用Bridge模式。

桥模式并不同于适配器模式,适配器模式其实是一个事后诸葛亮,当发现以前的东西不适用了才去做一个弥补的措施。桥模式相对来说所做的改变比适配器模式早,它可以适用于有两个甚至两个以上维度的变化。

结构型模式(二) 桥接模式(Bridge)的更多相关文章

  1. 结构型设计模式之桥接模式(Bridge)

    结构 意图 将抽象部分与它的实现部分分离,使它们都可以独立地变化. 适用性 你不希望在抽象和它的实现部分之间有一个固定的绑定关系.例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换. ...

  2. 设计模式GOF23(结构型模式:代理模式,适配模式,桥接模式,组合模式,装饰模式,外观模式,享元模式)

    结构型模式: – 分类: • 适配器模式.代理模式.桥接模式.装饰模式.组合模式.外观模式.享元模式 – 核心作用:是从程序的结构上实现松耦合,从而可以扩大整体的类结构,用来解决更大的问题.   结构 ...

  3. Linux下NAT模式和桥接模式的网络配置

        Linux下NAT模式和桥接模式的网络配置 最近在配置linux虚拟机的时候发现有很多坑,现在记录下来以防日后又跳到坑里. 我的运行环境是:主机 windows 7  虚拟机 Virtualb ...

  4. NAT模式、路由模式、桥接模式的区别

    NAT模式 NAT模式概述 NAT是"Network Address Translation"的缩写,中文意思是"网络地址转换",它允许一个整体机构以一个公用I ...

  5. 虚拟机网络连接模式中桥接模式和NAT模式的区别

    1.桥接模式:当虚拟机系统的网络连接模式为桥接模式时,相当于在主机系统和虚拟机系统之间连接了一个网桥,而网桥两端的网络都属于同一网络,主机和虚拟机是处于同一网络中的对等主机. 实例,在使用Xshell ...

  6. VMware虚拟机上网络连接模式bridged(桥接模式)

    VMware虚拟机上网络连接模式bridged(桥接模式)的实质就是虚拟机本身利用主机的网卡对外直接作为一个真实的物理主机存在. 也就是理论上此时的虚拟机和主机没什么关系,只是和主机公用了一块网卡,其 ...

  7. GoF23种设计模式之结构型模式之桥接模式

    一.概述         将类的抽象部分与实现分部分离开来,使它们都可以独立地变化. 二.适用性 1.你不希望在抽象和实现之间有一个固定的绑定关系的时候.例如:在程序运行时实现部分应可以被选择或切换. ...

  8. 【设计模式】Bridge模式(桥接模式)

    最近的一次面试中,被问到桥接模式,以前呢并没有很仔细的研究过这个设计模式,借此机会剖析一下. 先给出自己对这个模式理解后的源码: interface A{ void methodA(); } inte ...

  9. 七个结构模式之桥接模式(Bridge Pattern)

    问题: 当存在多个独立的变化维度时,如果仍采用多层继承结构,会急剧的增加类的个数,因此可以考虑将各个维度分类,使他们不相互影响. 定义: 将抽象部分与它的实现部分进行分离,抽象部分只保留最为本质的部分 ...

随机推荐

  1. JIRA学习

    Jira是Atlassian公司出品的一款事务管理软件.无论是“需求”,还是“BUG”,或是“任务”,都是“事务”的一种,所以Jira可以胜任非常多的角色:需求管理.缺陷跟踪.任务管理等等……因为Ji ...

  2. String初解

    String 类型是不可变的对象,因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后有一次看到一个代码段,如下: 这段代码返回的是ture,当时以为是f ...

  3. windows10 ubuntu子系统运行docker出现的问题

    前一篇笔记记录了安装的过程及错误处理办法,但是在正式使用docker时,却又出现新的问题: “docker: Cannot connect to the Docker daemon at unix:/ ...

  4. REST-framework之频率组件

    REST-framework之频率控制 一 频率简介 为了控制用户对某个url请求的频率,比如,一分钟以内,只能访问三次 二 自定义频率类,自定义频率规则 自定义的逻辑 ""&qu ...

  5. Linux进程的五个段

    目录 数据段 代码段 BSS段 堆(heap) 栈 数据段 用来存放可执行文件中已初始化的全局变量,换句话说就是存放程序静态分配的变量和全局变量: 代码段 代码段是用来存放可执行文件的操作指令,也就是 ...

  6. wstngfw中使用虚拟IP映射内网IP

    wstngfw中使用虚拟IP映射内网IP -------------------------------- Server01: IP: 192.168.195.73/24 GW: 192.168.19 ...

  7. 【转载】C#中通过Distinct方法对List集合进行去重

    在C#的List集合对象中,可以使用Distinct方法来对List集合元素进行去重,如果list集合内部元素为值类型,则Distinct方法根据值类型是否相等来判断去重,如果List集合内部元素为引 ...

  8. session 在PC端正常设置读取,在移动端无法正常读取

    一.背景 最近在做一个面向三端[H5.IOS.安卓]的短信验证码登录接口.发送短信验证码时,服务端通过 session 保存验证码的值.登录时,从 session 获取验证码和用户输入的验证码 相比较 ...

  9. [React] 函数定义组件

    函数定义组件的例子 function Welcome(props) { return <h1>Hello, {props.name}</h1>; } 该函数是一个有效的 Rea ...

  10. MySql时区修改

    1.查看当前时间 > select curtime(); #或select now()也可以+-----------+| curtime() |+-----------+| 15:18:10 | ...