写在前面

为方便读者,本文已添加至索引

“魔镜啊魔镜,谁是这个世界上最美丽的人?”月光中,一个低沉的声音回荡在女王的卧室。“是美丽的白雪公主,她正和小霍比特人们幸福快乐地生活在森林之中。”魔镜答道。“可恶!我才应该是最美的人,我要除掉你,白雪公主!”女巫开始用她的水晶球施展起诡异的妖术。

原本安宁的森林最近特别的闹腾,动物们个个都焦躁不安。小霍比特人之一的theWoodcutter(樵夫)在去伐木的路上发现了一头野熊的尸体。这头庞然大物的伤口上除了血淋淋的爪痕,竟然混杂有烧焦的痕迹。他急急忙忙回到住处,和小伙伴们商量对策。他们首先想弄明白的是,到底是什么入侵了自己的家园。

而这个世界的主人--时の魔导士,此刻正倚靠在木制的摇椅上,安详地抽着烟斗。他一回想起自己设计的这个虚拟的童话世界,脸上就露出了满意的笑容。他曾为小霍比特人们建造了武器工坊(见Factory Method笔记),Weapon是一个抽象类,它的子类包括Sword, Dagger, Bow等等都继承了Weapon的方法attack()。attack()能对敌人造成物理上的伤害……嗯,物理上的伤害。“似乎这个还不太完美啊。在魔法的世界里,武器是可以被附魔Enchanted的。”魔导士嘟囔道,“比如,附上火焰的剑能轻易刺穿易燃的装甲,附上寒冰的箭矢也可以用来灭火呀。这想法有趣多了,让我来加点新鲜的东西给孩子们吧。”

附魔后的武器(EnchantedWeapon)的确是可以通过继承Weapon来得到,比如下图:

但是如果这样设计会有很糟糕的结果。武器种类很多,如果日后还要添加别的类型的武器,比如传奇的武器(EpicWeapon)每个都带有史诗般的故事,装饰性的武器(DecoratedWeapon)比起杀伤力更注重外观等等。每添加一种武器分支,都会导致产生更多的子类。同时,如果我们修改了Weapon的接口,那后续改动将非常巨大。

于是,时の魔导士引入了Bridge(桥接)模式,它将抽象部分与实现部分分离,从而很好地解决了上面所提到的问题。

要点梳理

  • 目的分类

    • 对象结构型模式
  • 范围准则
    • 对象(该模式处理对象间的关系,这些关系在运行时刻是可以变化的,更具动态性)
  • 主要功能
    • 将抽象部分与它的实现部分分离,使它们都可以独立地变化
  • 适用情况
    • 我们不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换
    • 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时Bridge模式使我们可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充
    • 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译
    • 想在多个对象间共享实现(可能使用引用计数),但同时要求客户并不知道这一点
  • 参与部分
    • Abstraction:定义抽象类的接口;维护一个指向Implementor类型对象的指针
    • RefinedAbstraction:扩充由Abstraction定义的接口
    • Implementor:定义实现类的接口,该接口不一定要与Abstraction的接口完全一致;事实上这两个接口可以完全不同。一般来讲,Implementor接口仅提供基本操作,而Abstraction则定义了基于这些基本操作的较高层次的操作。
    • ConcreteImplementor:实现Implementor接口并定义它的具体实现。
  • 协作过程
    • Abstraction将Client的请求转发给它的Implementor对象
  • UML图

示例分析 - 元素附魔武器诞生

利用桥接模式,我们将各类武器抽象成Weapon类,它提供一个attack()的接口函数。对于武器的使用者,它只需操纵attack()即可。(发现这点和之前一样,对吧?但是别急)请看示例:

 class Weapon {
public:
Weapon();
virtual void attack();
protected:
WeaponImpl* getWeaponImpl();
private:
WeaponImpl* _impl;
}

注意到,Weapon它维护了一个对WeaponImpl的引用,而该抽象类定义了具体武器的操作的接口:

 class WeaponImpl {
public:
virtual act() = ;
virtual addEffect(const char*) = ;
protected:
WeaponImpl();
}

现在,我们可以将武器攻击动作的具体实现放在WeaponImpl的子类中,从而成功分离出Weapon类这个抽象概念。作为扩展,附魔武器EnchantedWeapon类如下:

 class EnchantedWeapon : public Weapon {
public:
EnchantedWeapon(char*);
virtual void attack();
private:
const char* _element;
} EnchantedWeapon::EnchantedWeapon(char* e):_element(e) {} void EnchantedWeapon::attack() {
WeaponImpl* impl = getWeaponImpl(); if (impl != ) {
impl->addEffect(_element);
impl->act();
}
}

EnchantedWeapon可以为武器附上不同程度的魔法效果,然后通过调用WeaponImpl类的act操作来行动。具体的WeaponImpl类则根据自身情况来实现act操作,剑可以用来挥舞和刺击,弓箭则要上满弦,等等。我们看这里的SwordImpl

 class SwordImpl : public WeaponImpl {
public:
SwordImpl(int);
virtual void addEffect(const char*);
virtual void act();
private:
int _damage;
char* _effect;
} SwordImpl::SwordImpl(int d) {
_damage = d;
} void SwordImpl::addEffect(const char* e) {
if (_effect)
{
delete _effect;
}
_effect = new char[strlen(e)+];
strcpy(_effect, e);
} void SwordImpl::act() {
if (_effect)
{
cout << "Wielding sword with " << _effect << endl;
}
else
{
cout << "Wielding sword" << endl;
}
}

SwordImpl

同样的对于BowImpl来说,效果是在箭矢上的:

 class BowImpl : public WeaponImpl {
public:
BowImpl(int);
virtual void addEffect(const char*);
virtual void act();
private:
int _damage;
char* _effect;
} BowImpl::BowImpl(int d) {
_damage = d;
} void BowImpl::addEffect(const char* e) {
if (_effect)
{
delete _effect;
}
_effect = new char[strlen(e)+];
strcpy(_effect, e);
} void BowImpl::act() {
if (_effect)
{
cout << "Shoot an arrow with " << _effect << endl;
}
else
{
cout << "Shoot an arrow" << endl;
}
}

BowImpl

一个简单的UML图来说明上面的模式:

在这套框架下,无论我们是继续添加EpicWeapon,还是具体的DaggerImpl等,都非常方便。顺便说下,我们可以用Weapon提供的getWeaponImpl方法来获得想要的武器实现。具体代码在此就不作详解了(可以利用抽象工厂或者单例等对象创建型模式来做)。

特点总结

Bridge模式有以下几个优点:

  1. 分离接口及其实现部分。一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现。将Abstraction与Implementor分离有助于降低对实现部分编译时刻的依赖性,当改变一个实现类时,并不需要重新编译Abstraction类和它的客户程序。为了保证一个类库的不同版本之间的二进制兼容性,一定要有这个性质。
  2. 提高可扩充性。我们可以独立地对Abstraction和Implementor层次结构进行扩充。
  3. 实现细节对客户透明

需要注意的是,如果我们仅有一个Implementor,可以没有必要创建一个抽象的Implementor类。这是Bridge模式的退化情况;在Abstraction与Implementor之间有一种一对一的关系。

写在最后

森林里的中间有一只凶狠的魔法生物,混身散发着火焰,周围是烧焦的树木。它似乎被顽强的霍比特人激怒了,咆哮了起来。仅凭普通的武器,小霍比特人们无法战胜这只凶兽,他们中已有好几人身负重伤了。在千钧一发之刻,武器工坊发出耀眼的光芒,白雪公主从中拿出了一把镶满冰晶的奇弓,附着了寒冰的魔法。公主拉起弓弦瞄准了火兽,“真正的战斗现在才开始呢!” ......

今天的笔记就到这里了,欢迎大家批评指正!如果觉得可以的话,好文推荐一下,我会非常感谢的!

[学习笔记]设计模式之Bridge的更多相关文章

  1. [学习笔记]设计模式之Chain of Responsibility

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 最近时间比较紧,所以发文的速度相对较慢了.但是看到园子里有很多朋友对设计模式感兴趣,我感觉很高兴,能够和大家一起学习这些知识. 之前的 ...

  2. [学习笔记]设计模式之Facade

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 Facade(外观)模式定义了一个高层接口,它能为子系统中的一组接口提供一个一致的界面,从而使得这一子系统更加容易使用.欢迎回到时の魔 ...

  3. [学习笔记]设计模式之Decorator

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 Decorator(装饰)模式,可以动态地给一个对象添加一些额外的职能.为了更好地理解这个模式,我们将时间线拉回Bridge模式笔记的 ...

  4. [学习笔记]设计模式之Abstract Factory

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 在上篇笔记Builder设计模式中,时の魔导士祭出了自己的WorldCreator.尽管它因此能创造出一个有山有树有房子的世界,但是白 ...

  5. [学习笔记]设计模式之Builder

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 作为一个新入职的魔导士呢,哦不,是程序员,我以为并没有太多机会去设计项目的软件架构.但是,工作一段时间之后,自己渐渐意识到,哪怕是自己 ...

  6. [学习笔记]设计模式之Adapter

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 Adapter(适配器)模式主要解决接口不匹配的问题.为此,让我们要回到最初Builder模式创建平行世界时,白雪公主和小霍比特人的谜 ...

  7. [学习笔记]设计模式之Prototype

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 在笔记Builder模式中,我们曾见到了最初用于创建平行世界的函数createWorld,并且它是Mage类的成员函数(毕竟是专属于魔 ...

  8. [学习笔记]设计模式之Command

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 在上篇Chain of Responsibility(职责链)模式笔记中,我们学习了一种行为型设计模式.今天,我们继续这一主题,来学习 ...

  9. [学习笔记]设计模式之Composite

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 在Composite(组合)模式中,用户可以使用多个简单的组件以形成较大的组件,而这些组件还可能进一步组合成更大的.它重要的特性是能够 ...

随机推荐

  1. 浅析PC机串口通讯流控制

    转自浅析PC机串口通讯流控制 我们在串行通讯处理中,常常看到RTS/CTS和XON/XOFF这两个选项,这就是两个流控制的选项,目前流控制主要应用于调制解调器的数据通讯中,但对普通RS232编程,了解 ...

  2. NGINX+UWSGI部署生产的DJANGO代码

    并且NGINX不用ROOT帐户哟. 1,编译安装NGINX及UWSGI及DJANGO,不表述 2,将NGINX文件夹更改为普通用户拥有.但执行文件NGINX仍为ROOT,运行如下命令加入特殊权限标志位 ...

  3. 任正非:华为三十年大限快到了 想不死就得新生(建立战略预备队)cool

    华为心声社区官方微信今日发布了任正非8月15日在华为公司内部做的关于战略预备队建设汇报的讲话.讲话内容中提到,华为公司需要组织.结构.人才等所有一切都变化,通过变化使新的东西成长起来.   任正非表示 ...

  4. centos挂载移动硬盘

    yum install ntfs-3g sudo mount -t ntfs-3g /dev/sdc1 /mnt/mobiledisk https://tuxera.com/opensource/nt ...

  5. bzoj2705

    一个常用的结论(方法) 只要知道gcd(i,n)=L 的i的个数s,我们就能很轻易得出答案 gcd(i,n)=L gcd(i/L,n/L)=1 不难得到这样的s=与n/L互质的个数=phi(n/L) ...

  6. ☀【CSS3】背景图片

    CSS3全新的背景图片方案http://www.cnblogs.com/rubylouvre/p/3401125.html

  7. MongoDB的备份(mongodump)与恢复(mongorestore)

    备份: D:\mongodb2.4.3\bin>mongodump -u101.key -p123 -h 127.0.0.1:27017 -d mydb -o d:\backup 恢复: D:\ ...

  8. 手动启动jenkins

    无论Linux还是windows, jenkins都是作为一个后台服务存在的. 所以在Linux下,手动启动或停止jenkins: service jenkins start service jenk ...

  9. (转)Python:self

    原文:http://www.douban.com/group/topic/19376685/ 这是对前面一个php程序员问python方法为什么要手写一个self的回答,当时那个帖非常的热闹,但是下面 ...

  10. python中类的继承

    python中类的继承 在python中面向对象编程中实现继承,以下面一个实例进行说明. class SchoolMenber(): # __init__类似于c++中的构造函数 # __init__ ...