BRIDGE模式 —— 所谓伊人,在水一方
junguo
Bridge模式的中文名称是桥接模式,该模式的目的是将抽象部分和它的实现部分分离,使它们都可以独立的变化。继续以例子来完成对该模式的学习。
蒹葭苍苍,白露为霜。所谓伊人,在水一方。
溯洄从之,道阻且长。溯游从之,宛在水中央。
蒹葭凄凄,白露未晞。所谓伊人,在水之湄。
溯洄从之,道阻且济。溯游从之,宛在水中坻。
蒹葭采采,白露未已。所谓伊人,在水之涘。
溯洄从之,道阻且右。溯游从之,宛在水中沚。
这首《蒹葭》取自《诗经》。王国维在他的《人间词话》中评述说:“《诗?蒹葭》一篇,最得风人深致”。简单的场景切换,最直接的感情描写,成了千百年来最能体现诗人情致的诗。我感觉诗在形式上最重要的是它的语言,简单凝练是真正的境界。现今的白话诗,大多被写的晦涩难懂,读完后佩服诗人到无话可说,写的字都认识,但是就不明白放到一块是什么意思。一直搞不懂为什么要写成那样?白话文写的诗歌比古文都难懂很多,有人说这是一个不需要诗人的时代,但我想也许是诗人漂离了时代,他们也许走到了时代的前列,但我又觉得不见得过多少年后就会有人愿意花时间去琢磨晦涩的东西。除了形,就是意了。诗歌在意上追求境界,应该给人遐想的空间。如“有的人死了,他还活着;有的人活者,他已经死了”直白到无任何情趣的诗,也让人毫无兴趣;但这样的诗一度是中国诗的主流,而穆旦那样的优秀诗人则被排挤出了主流,排挤到无法继续写诗的地步。继续说《蒹葭》,读完诗歌会感觉到这样一个场景,青山绿水,远处隐隐约约一个少女的身影。近处一个男子在急切的寻找着船只,眺望着远方的路。而他最终没找到船,望不到路,焦虑忧伤尽显脸上。也许你会和我一样发现,这个寻路人就是自己。我在想程序是否也有境界,这个境界又该有什么样样的标准呢?
这样的诗歌意境也许可以做成一款游戏。记得上学的时候有款游戏《心跳回忆》,类似于养成游戏,游戏的内容就是玩家通过不断的邀请游戏中的美女约会,送礼物给美女等方式来增加美女对玩家的好感,好感增加到一定程度,她就会接受玩家的求婚。很少有人有耐心玩完这款破游戏,但我们同学中有位仁兄乐此不疲。考试前几个钟头还在玩,结果考后挂了几科要降级了。当得知降级的噩耗的时候,哥们掉了几颗眼泪,狠狠心又坐到了电脑面前,继续他的《心跳回忆》,10多个小时过后,当大家还都在梦乡的时候。他就各个宿舍游荡,看到有人醒来就异常兴奋得告诉人家,《心跳回忆》中的女主角他又追到了一个。降级的郁闷就这样被扫空了。现在,听说这位兄弟找到的女朋友很漂亮。看到仍是单身的同学,也许他该窃笑了:得亏哥们当年还练过。
我们要实现的功能没有那么复杂了,此处主要是设想帮游戏设计一个场景,把青山绿水画到屏幕上。由于山可能不只一座,水也可能有多处。我们想把它们封装成单独的类,由客户随意去组合,组合成不同形状的图景。那么我们的类图可以提炼成如下的形式:
![](image/d41d8cd98f00b204e9800998ecf8427e.jpg)
有了类图,我们就可以进行开发了。一般来说,游戏开发的图景是写实性质的,类似于西方油画。但经调查发现,有这么一帮书呆子,听到“书中自有颜如玉”的古训后,遍翻古书寻找美女,后来发现不过瘾,想到游戏中寻找自己的梦中情人。这批人酷爱中国古典文化,重意不重形,喜欢国画上的美女,当然山水也应该传统的水墨画。为了吸引这批呆子,决定在游戏中提供水墨画的场景。这样我们的类也需要进行相应的变化,它需要支持油画和水墨两种风格。想想,我们该如何实现这一功能呢?最直接想到的就是通过继承来实现,那么我们的类图可能变成这样。
![](image/d41d8cd98f00b204e9800998ecf8427e.jpg)
图中我们帮油画和水墨都提供了接口,而山水则分别通过油画和水墨来继承。这样明显的问题是,我们需要为山水各提供两套代码。有这个必要吗?只是造图风格上不同,但过程是一样的。接着想,如果我们新添加一个新场景进来,比如说天空和陆地,也需要单独的类来实现。那么这时候我们又要为每个场景添加两个类。接着想,后来发现,有哥们喜欢后印象主义画派,还有哥们喜欢抽象主义(写完后,想想了,发现抽象主义还是比较容易实现的,画个三角形代表山,画两个弧线代表水,只要颜色取绚了就是抽象作品了),那么再把这些风格都加入到该模式中,会发生什么情形呢?完了,已经实现过的山水类都需要重新实现一次。接着想,客户端调用又会有什么情形呢?好多类啊,该选哪个呢?当然你也可以帮他实现一个抽象工厂来减轻负担,但这个工厂的实现一样烦琐。看到了这么多问题,其实总结起来原因就是一个,通过继承我们把风格和具体的实物(山,水等)固定到了一起,无法抽离出来,导致了类的膨胀。那么该如何解决这个问题呢?方法就是把这两样东西给分离出来,让它们各自实现各自的代码。怎么实现呢?来看类图:
![](image/d41d8cd98f00b204e9800998ecf8427e.jpg)
我们把不同风格给分离了出来。它们有一个基类Img,主要用来实现基本的画图功能,比如画线,画圈等内容(我们这边只简单列出了画垂直线和水平线)。而我们的场景也是一个单独的类,在这些类中调用Img接口提供的基本画图功能来实现不同的图形。抽象出来后,我们再回头看上面提到的问题。假如我们添加一个具体的实物进来,比如说要添加天空进来,那么我们只要添加一个类就好了,只要调用保证它调用Img接口来实现就可以有不同的风格了。如果需要添加另一种风格进来,如后印象主义风格,那我们也不需要改变原有的类,只要先添加一个类进来就好了。扩展是不是方便了很多?还是来看看模拟的代码(简陋了些,我只能用文字表达表达了),先来看看风格类所需要的代码:
~~~
//风格接口
class Img
{
public:
//画水平线
virtual void DrawHorLine() = 0;
//画垂直线
virtual void DrawVerLine() = 0;
protected:
Img(){}
};
//中国画风格
class ChineseImg : public Img
{
public:
void DrawHorLine()
{
cout << "水墨画水平线" << endl;
}
void DrawVerLine()
{
cout << "水墨画垂直线" << endl;
}
};
//油画风格
class CanvasImg : public Img
{
public:
void DrawHorLine()
{
cout << "油画水平线" << endl;
}
void DrawVerLine()
{
cout << "油画垂直线" << endl;
}
};
~~~
这边要实现的功能其实就是提供画图的基本方法,比如画线或者画圆等动作(我不太清楚,现在图形库能不能帮我们实现水墨画风格的图形,但我想将来应该可以的)。我们这里的代码比较简单,不做太多的解释。接着看看实体类的实现。
~~~
//场景抽象类
class Scene
{
public:
virtual void DrawBorder() = 0;
Img *GetImg()
{
return m_pImg;
}
virtual ~Scene()
{
delete m_pImg;
}
protected:
Scene()
{
//此处的原因,文章里分析
m_pImg = new CanvasImg();
}
private:
//拥有一个Img的指针,通过它来调用画图功能
Img *m_pImg;
};
//山的类
class Hill : public Scene
{
public:
void DrawBorder()
{
DrawHill();
}
void DrawHill()
{
cout << "三条垂直线就代表山了!" << endl;
Img *pImg = GetImg();
pImg->DrawVerLine();
pImg->DrawVerLine();
pImg->DrawVerLine();
}
};
//水的类
class Water : public Scene
{
public:
void DrawBorder()
{
DrawWater();
}
void DrawWater()
{
cout << "三条水平线就代表水了!" << endl;
Img *pImg = GetImg();
pImg->DrawVerLine();
pImg->DrawVerLine();
pImg->DrawVerLine();
}
};
~~~
首先注意到一点,就是Scene的构造函数的实现中有这样的语句:“m_pImg = new CanvasImg()”,而且Scene类中没有改边m_pImg的方法,这样做的原因是因为一般来说Bridge是对于整个程序风格的设置。你配置了什么样的风格,整个程序运行的过程中风格就是什么。因为一般来说Bridge主要是适用于跨平台的开发,在Linux中与在Windows中,画图的方法肯定不相同,通过Bridege可以解决这个问题,但在运行过程中肯定不应该改变Bridge的类型。可以通过配置文件来实现,这样的话,在m_pImg生成的地方通过读取配置文件,根据文件类型来创建m_pImg就好了。当然你想在运行过程中改变m_pImg类型也可以,看具体情况了。再看看调用:
~~~
int main(int argc, char* argv[])
{
Hill h;
Water w;
h.DrawBorder();
w.DrawBorder();
return 0;
}
~~~
通过创建的Hill或者Water类去画图就可以了。
我们再来看看Bridge的定义:将抽象部分和它的实现部分分离,使它们都可以独立的变化。其实这里的抽象用的并不贴切,但想不到太好的词,人类语言的表达能力是有限的。这里所说的抽象,并不是我们所说的接口的抽象。在例子里抽象指的就是Img及其子类了,用来画图的对象。而实现部分指的就是我们的Hill和Water类了。
Bridge看完了,是不是感觉有些眼熟呢?你是不是和我一样迟钝,想半天,还需要翻看书才想起来?回忆一下我们之前谈过的策略模式,看看类图。两个模式不仅相似,而且相似的令人发指,完全是一样的。但为什么会被划成两个模式呢?其实主要是它们要解决的问题不相同,策略模式针对的是类中所属的单个对象或者单个方法,由于这样的对象或者方法会有不同的实现方法,可能需要运行时刻动态改变。那么我们可以用策略模式来改变这样的功能。而Bridge针对的是整个类,根据不同的Bridge,整个类的风格将发生变化。也可以这样理解策略模式针对的是类中单个对象,这个对象可能影响到类中少数的几个函数。而Bridge模式,将影响到类中决大多数的方法。
参考书目:
1, 设计模式——可复用面向对象软件的基础(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英军等译 机械工业出版社
2, Head First Design Patterns(影印版)Freeman等著 东南大学出版社
3, 道法自然——面向对象实践指南 王咏武 王咏刚著 电子工业出版社
4, 人间词话手稿本 王国维 著 吴洋注释 内蒙古出版社