[学习笔记]设计模式之Bridge

时间:2023-02-25 15:33:45

写在前面

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

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

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

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

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

[学习笔记]设计模式之Bridge

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

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

要点梳理

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

[学习笔记]设计模式之Bridge

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

利用桥接模式,我们将各类武器抽象成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图来说明上面的模式:

[学习笔记]设计模式之Bridge

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

特点总结

Bridge模式有以下几个优点:

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

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

写在最后

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

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

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

  1. &lbrack;学习笔记&rsqb;设计模式之Chain of Responsibility

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

  2. &lbrack;学习笔记&rsqb;设计模式之Facade

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

  3. &lbrack;学习笔记&rsqb;设计模式之Decorator

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

  4. &lbrack;学习笔记&rsqb;设计模式之Abstract Factory

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

  5. &lbrack;学习笔记&rsqb;设计模式之Builder

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

  6. &lbrack;学习笔记&rsqb;设计模式之Adapter

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

  7. &lbrack;学习笔记&rsqb;设计模式之Prototype

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

  8. &lbrack;学习笔记&rsqb;设计模式之Command

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

  9. &lbrack;学习笔记&rsqb;设计模式之Composite

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

随机推荐

  1. ASP&period;NET Core应用针对静态文件请求的处理&lbrack;4&rsqb;&colon; DirectoryBrowserMiddleware中间件如何呈现目录结构

    和StaticFileMiddleware中间件一样,DirectoryBrowserMiddleware中间本质上还是定义了一个请求地址与某个物理目录之间的映射关系,而目标目录体现为一个FilePr ...

  2. bzoj4525&colon; &lbrack;Usaco2016 Jan&rsqb;Angry Cows

    二分. #include<cstdio> #include<algorithm> #include<cstring> using namespace std; + ...

  3. angularjs Failed to read the &&num;39&semi;selectionStart&&num;39&semi; property from &&num;39&semi;HTMLInputElement&&num;39&semi;&colon;

    在找angularjs input(type='number')在获取焦点的时候,文本框内容选中效果,参考了:Select text on input focus,我直接复制他的code之后,在ion ...

  4. 编译&commat;Override报错

    有时候Java的Eclipse工程换一台电脑后编译总是@override报错,把@override去掉就好了,但不能从根本上解决问题,因为有时候有@override的地方超级多. 这是jdk的问题,@ ...

  5. YYLabel 自动布局 富文本文字点击事件

    YYLabel显示多行除了需要设置numberOfLines = 0以外,还需要设置preferredMaxLayoutWidth最大的宽度值才可以生效多行效果 YYLabel中的NSMutableA ...

  6. js中的数据类型、以及浅拷贝和深拷贝

    一.js中的数据类型 1.基本类型(值类型):Undefined.Boolean.String.Number.Symbol 2.引用类型:函数.数组.对象.null.new Number(10)都是对 ...

  7. Find out where to contain the smartforms

    Go to table E071 and give smarforms name and it will give the transport req for that. Run SE03, choo ...

  8. MySQL Limit优化(转)

    原文:http://bbs.landingbj.com/t-0-240894-1.html 首先,我们看一个分页SQL: SELECT time,pageFROM `l_not_200_page`WH ...

  9. C&plus;&plus; 日期 &amp&semi; 时间

    C++ 标准库没有提供所谓的日期类型.C++ 继承了 C 语言用于日期和时间操作的结构和函数. 为了使用日期和时间相关的函数和结构,需要在 C++ 程序中引用 头文件. 有四个与时间相关的类型:clo ...

  10. js &period;map方法

    map这里的map不是“地图”的意思,而是指“映射”.[].map(); 基本用法跟forEach方法类似: array.map(callback,[ thisObject]); callback的参 ...