名称:Bridge模式
定义:将抽象部分与他的实现部分分离,使得他们可以独立的变化。
UML:
个人理解:
所谓桥接模式,按照Gof黄钟大吕的说法就是“将抽象部分与他的实现部分分离”。什么叫“将抽象部分与他的实现部分分离”呢?
先说抽象。我认为,当我们构建系统的时候,如果我们无法把握一个事物或者集合(比如说它复杂或者多变),就用一个拥有它一部分或者全部属性与行为的事物取代替他与系统其它部分进行关联,就叫抽象。举个例子说说我的理解。在一起在过去的100年中,人类的知识规模获得了爆炸性的膨胀,但是就单个普通人来说,今天的张三并不被认为比100年前的张三博学多少。我知道很多人小时候梦想过拿着自己的中学课本回到过去,赶在牛顿之前提出个运动定律什么的。我不得不告诉你,这并不可行,因为课本上的知识是被“抽象”过的。F=m*a绝没有他看起来那么简单,为了得到他,你要站在“巨人的肩膀上”,大量的观察各种运动,发现一个叫“速度变化趋势”东西起别名叫“加速度”,然后发明一个叫微积分的东西,再用微积分推理出F=m*a的微分形式,转化为积分形式,最后得到恒力下的特殊公式。在计算F=m*a的时候,其实背后运行的是最精密的微积分和物理过程,只是这些你都不知道,它们被“抽象”在F=m*a背后了。
但是当我们要解决一个受恒力问题时,事情就变的简单多了,不需要微积分,只需要用F=m*a这个抽象出来的公式,与题设的参数进行关联,就可以得出结果。F=m*a就是抽象,顺着这个思路,实现就是那些微积分和物理过程了。
在编程语言中,抽象有很多叫法:接口,抽象类,SDK,API,框架……我倒是觉得说得是一个东西,只是抽象的东西和关联的对象不一样罢了。
原话中还有一个说法叫分离。我总结了一下,觉得从相对有关变的相对无关就是分离。呵呵,这样等于没解释:)
比如我写一个某设备的通用客户端,要求在同一界面下调用SDK操作不同厂家的产品,各厂家都提供了自己的SDK。假如我用最“不分离”的方法写这个程序,那么就会直接包含每个SDK,在界面里根据判据不同调用相应SDK,比如这样:
- #define MILL_A 101
- #define MILL_B 102
- ......
- BOOL Opt1(int iDeviceType)
- {
- switch iDeviceType
- {
- case MILL_A:
- MILL_A_Opt1();
- break;
- case MILL_B:
- MILL_B_Opt1();
- break;
- ......
- default:
- break;
- }
- }
这就是“抽象和实现没有分离”,“设备”这个抽象概念与设备的不同实现是混杂在一起的,我在构建整个系统的时候不能用“设备”这个东西与其他部分进行关联,只能用设备的某一种实现(即不同SDK)1,。于是每当有新品牌的设备加进来后,我都要修改上面的switch结构,加进一个新的case。
为了避免麻烦,我只好将抽象的设备与设备的实现分离. 我先要写一个设备基类(也可以说是接口,C++里不是很区别这个),包含各SDK里的公有方法(事实上是绝大多数方法),然后派生不同子类封装不同SDK。比如:
- class CDevice
- {
- public:
- virtual void Opt1(void) = 0;
- ......
- };
- class CDevice_A : public CDevice
- {
- public:
- virtual void Opt1(void)
- {
- MILL_A_Opt1();
- }
- ......
- };
- class CDevice_B : public CDevice
- {
- ......
- };
- CDevice* Device[50];
- ......
- void Opt1(int iDeviceNo)
- {
- Device[iDeviceNo]->Opt1();
- }
- ......
这样通过简单的继承,我们不必再每次加设备都修改主程序。我们更可以把设备子类放在dll里面,运行时根据设备表调用不同的dll创建实例,在主程序里我们只需要面对Device类指针。原本蕴藏在各种设备中的抽象设备这个概念被提取出来构造成一个基类,这就是抽象和实现的分离。
依照上面的结构写的软件对于接入设备的要求着实可以高接低档一阵,但是当设备变得比较复杂的时候,问题就出来了。比如现在要增加一种叫保护服务器的东西,当设备遇到某状况时要通知保护服务器运行某个保护预案。根据配置文件,有的客户要求UDP发送,有的客户要求TCP,有的客户的服务器有应答,有的没有,有的有心跳检测回执,有的没有……这就比较麻烦了,要是过上一段时间又出现了个什么日志服务器怎么办?
要解决问题就要进一步抽象,并与实现分离。什么?不是已经抽象、分离了么?是的,但只是从SDK这个维度上抽象分离了,这是不够的。
什么叫“维度”呢?呵呵,这是我借用的词。类比一下数学,我们在一维坐标中可以描述一条直线上的点,比如点1在(-1)处,点2在(3)处,但是要描述一个一个平面上的点,就要用二维坐标,比如点1在(1,1),点2在(1,3)。我们总结了点在一个方向上的位置这个概念,抽象出X轴,又在另一个方向抽象出Y轴,并于具体点的位置分离开来。于是可以说当我们用二维坐标的时候,就完成了两个维度上的抽象与分离。
现在回到程序,这时候我们的“Y轴,Z轴”就是“通知方式”和“日志上传方式”这些抽象概念,我们需要把通知这些模块单独做成基类,如下:
- class CMessager
- {
- pubclic:
- ......
- virtual void SendMessager() = 0;
- ......
- };
- class CMessager_1 : public CMessager
- {
- ......
- };
- class CMessager_2 : public CMessager
- {
- ......
- };
- ......
- class Loger
- {
- public:
- ......
- virtual void UpLoadLog() = 0;
- ......
- };
- .....
好了,剩下的工作就是把Device与Messager,Loger组合起来,怎么连接呢?C++的方法就是在第一维基类里包含其他维度基类的指针,就这么简单。
当然你也可以把Message和Log的实例封装在DLL里,那又是另一个故事了:
- class CDevice
- {
- private:
- CMessager* m_pMessager;
- protected:
- CMessager* GetMessager(void)
- {
- return m_pMessager;
- }
- public:
- CDevice(void)
- {
- m_pMessager = GetMessagerInstance(); //这里往往通过配置文件生成实例
- };
- virtual void SendMessage(void) = 0;
- };
- class CDevice_A : public CDevice
- {
- private:
- ......
- protected:
- ......
- public:
- ......
- SendMessage(void)
- {
- GetMessager()->SendMessage
- }
- };
- ......
到这里就是我理解的桥接模式了。其核心是我把他理解为对一个类进行多维抽象与分离,或者说在多个取向(抽象)上度量(不同实现)一个类。值得注意的是,按照这样的理解似乎并不能区别Bridge和Strategy。是的,这两个模式都符合上面的说法
,事实上我把他们看成同一个行为外不同场合和层面上的体现。从这个角度说,“多维抽象与分离”这个说法似乎可以视为Bridge和Strategy的抽象:)
最后想说假如你是个初学者,千万别信我!我也是初学者:)