ADAPTER模式 —— 田单火烧牛尾破燕军
junguo
Adapter的中文翻译是适配器,它的目的是将一个类的接口转化为另外一个类的接口,这样可以使原本由于接口不同而不能一起工作的类可以一起工作。这个模式比较简单,我们还是通过例子来具体看看。
战国时期,燕国大将乐毅带兵攻打齐国。五年的时间,占领齐国七十多座城池,只剩两城莒和即墨还在坚守,但也兵少势薄,眼看着齐国就要被灭。而一个人改变了齐国的命运,这个人就是田单。乐毅攻齐的时候,田单不过是一个齐国不知名的小官。他和别人一样无法掌握自己的命运,等乐毅大军到来的时候,他也不得不逃跑。在逃跑的过程中,他显示出了自己过人的智慧。他让自己宗族的人,把车轴两端突出的部分全部锯掉,并用铁箍将车轮包上。逃跑的时候,场面混乱,大多车辆被撞的轴断车坏,做了燕国的俘虏。而田单和他的族人顺利的逃跑了出来。田单来到了即墨,这时候即墨大夫出城迎击燕军,结果战死疆场。这时候大家由于田单逃跑过程中表现出的智慧,选举他做了将军,对抗燕军。赶上这时候燕国国君昭王逝世,而他的继任者惠王与乐毅之间有矛盾。田单使反间计,让惠王撤换了乐毅,而乐毅无奈之下逃到了赵国。
走了乐毅,田单开始了自己的反攻大计。他先是让城中的人吃饭的时候必须祭祖,结果飞鸟都跑到城中来觅食,城外的燕军都感觉奇怪。他对别人说:神来救我们了,而且神人来做我的老师。他选了一个小卒作了他的老师,对别人说这是神师。每次发号施令,都说是神师的主意。然后他告诉别人:我最怕的是燕国人割掉齐国降兵的鼻子,作战的时候让这些人走在队伍的前面,这样的话,齐国就败了。结果城外的燕军听到这样的消息,真的割掉了齐国降兵的鼻子。这样一来,齐国守军都大怒,坚定了守城的决心。接着他又说:我还怕燕军掘我们城外的坟墓,这样就羞辱我们的先人了,是件很寒心的事。接口燕军真的开始挖掘坟墓,把死人拿出来烧。城中的人看到这样的情景,都痛苦流涕,都准备与燕军死战到底。这时候田单又找人给燕军送礼物,并对燕军说,齐国人准备投降了。结果燕国放松了警惕。
这时候田单在城中找了一千多头牛,并作了大红绸的衣服,并在衣服上面画上了五颜六色的龙形。把武器绑在了牛角上,在牛尾上绑了用油脂浸灌过的芦苇。在半夜的时候,在城里凿开数十个缺口,点燃牛尾把牛放了出去。牛尾受热,向燕军冲了过去。燕军大乱,看到的是五彩龙形,碰到非死即伤。这时候田单又组织城中的人敲锣打鼓,搞的惊天动地。燕军心胆俱裂,只知向前逃窜,田单带人一路追杀,结果齐国失去的七十多座城池也都收复了回来。
太史公评价到:兵以正合,以奇胜。善之者,出奇无穷。奇正还相生,如环之无端。夫始如处女,適人开户;後如脱兔,適不及距:其田单之谓邪!
看完这段故事,开始我觉得燕国将领也太可爱了,被田单牵着鼻子走。后来想想,连李洪志这种白痴的法 轮功(靠,这个词提都不让提,不在中间插入字符根本发布不了)都有人信,也没有什么事情想不通了。如果让田单也搞邪教,估计要比李大师强几百倍。
好了,故事扯到这里了。接着描述我们要用到的例子,上面的故事中出现了牛群破敌的例子。牛群作为一个对象出现在了我们的程序中,我们为它定义了一个类:
~~~
class Bulls
{
public:
void Wrestle();
};
~~~
而在我们之前出现过的例子中,有很多Army的例子:
~~~
class Army
{
public:
void Assault(){}
};
~~~
事实上牛群和军队的大部分操作都类似(如这里的Assault和Wrestle都代表攻击),只是接口并不一样。现在我们想把牛群也加到这样的程序中,并且使它们可以一起运行。因为之前我们的程序中,可能有很多这样的代码:
~~~
void test(Army *pArmy)
{
pArmy->Assault();
}
~~~
我们不想去改变这些代码。那该怎么办呢?这就是Adapter的作用了。先看类图:
![](image/d41d8cd98f00b204e9800998ecf8427e.jpg)
很简单了,就是先增一个新类BullsArmy,让它继承Army。而聚合Bulls,通过对Bulls的调用来实现Army的功能。
我们还是简单看看代码:
~~~
class Bulls
{
public:
void Wrestle()
{
cout << "牛群在攻击" << endl;
}
};
class Army
{
public:
void virtual Assault()
{
cout << "军队在攻击" << endl;
}
};
//新增的适配器类
class BullsArmy : public Army
{
private:
Bulls *m_pBulls;
public:
BullsArmy()
{
m_pBulls = new Bulls();
}
~BullsArmy()
{
delete m_pBulls;
}
void Assault()
{
m_pBulls->Wrestle();
}
};
~~~
再来看看它的调用:
~~~
void test(Army *pArmy)
{
pArmy->Assault();
}
int main(int argc, char* argv[])
{
Army* pArmy = new Army;
Army *pBulls = new BullsArmy();
test(pArmy);
test(pBulls);
return 0;
}
~~~
这样我们就将牛群和军队统一了起来,可以让它们一起合作。以上提供的方法中我们通过BullsArmy聚合Bulls来实现了功能。事实上也可以通过继承的方式来完成,我们还是简单看看类图:
![](image/d41d8cd98f00b204e9800998ecf8427e.jpg)
这样BullsArmy改为了从Army和Bulls来继承,仍然是通过调用Bulls的功能来实现Army的功能。这里需要注意的一点是,BullsArmy对Bulls的继承应该采取私有继承,这样Bulls的成员函数都成了它的私有成员函数,就不会把这些信息暴露给用户。我们还是简单看看代码,变化的只有BullsArmy的代码。
~~~
class BullsArmy : public Army,private Bulls
{
public:
void Assault()
{
Wrestle();
}
};
~~~
可以看到这两种方式都可以完成类的适配。前一种是通过聚合而来的,属于对象适配器;而后一种是通过继承完成的,属于类适配器。我们简单对比一下它们的优缺点:类配器相对简单一些,不需要保存被适配类的指针。而且通过重载可以很容易的改变被适配类原有的功能。但不够灵活,如当Bulls类有子类的时候,我们想适配它所有的子类,通过类适配器,是无法直接通过父接口来适配所有子类的,你可能需要为每个子类来填加适配器。如果你是通过对象适配器,就很容易实现这样的功能了。
适配器在实际开发中应用的比较广泛,概念也被扩展。适配器是STL的重要内容之一,感兴趣的可以找STL资料看看。
参考书目:
1, 设计模式——可复用面向对象软件的基础(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英军等译 机械工业出版社
2, Head First Design Patterns(影印版)Freeman等著 东南大学出版社
3, 道法自然——面向对象实践指南 王咏武 王咏刚著 电子工业出版社
4, 史记 网上找到的电子档