乱砍设计模式之十一
最后更新于:2022-04-01 14:37:06
剩余的模式
junguo
FACADE(外观)模式:是为子系统中的一组接口提供一个一致的界面。该模式理解起来还是比较容易的,举个例子:
~~~
class Eye
{
public:
Draw(){}
};
class Mouse
{
public:
Draw(){}
};
class Nose
{
public:
Draw(){}
};
~~~
我们有眼睛,嘴,鼻子这样的类。一般来说脸的组成是一定的,那我们就可以提供一个Face类,如下:
~~~
class Face
{
public:
Draw()
{
Eye LiftEye;
Eye RightEye;
Mouse mouse;
Nose nose;
liftEye.Draw();
RightEye.Draw();
mouse.Draw();
nose.Draw();
}
};
~~~
一般情况下,Eye,Mouse和Nose的使用都是这样的一个过程。而Fa?ade的目的就是为一般情况提供一个简化的接口,使用者直接调用Face就可以了,不需要自己去调用Eye等类了。
FLYWEIGHT(享元)模式:应用共享技术有效地支持细粒度的对象。这个模式,我自己想不到太好的例子,简单的把《设计模式》上的例子在这儿再序述一下。假如我们做一个文本编辑器,准备把每个字符做为一个对象,那么我们可能有这样一个类:
~~~
class Character
{
private:
CFond _fond;
Color _color;
char _charcode;
public:
Draw(){}
};
~~~
可以看到每个字符都包括字体,颜色等信息。这样的话,每个字符都会占用比较大的空间,而我们知道来般来说一篇文章中有很多重复的字符(尤其是英文,总共就那么26个字母),为每个字符都提供一个对象,是种浪费。这时候就可以利用FLYWEIGHT模式,可以提供一个链表,把出现了的字符组合放到链表中。这样使用字符的时候,只要有它在链表中的位置就可以了(可能是个整数,也可能是一个指针,总之比Character会小很多)。具体如何去用,大家可以参考《设计模式》一书。
PROXY(代理)模式:该模式其实很简单,目的就是帮助远程对象或者占用空间比较大的对象提供一个代理接口,这样只有在真正需要被代理类的时候,才把它载入内存。还是《设计模式》上的例子,简单看一下:
~~~
class ImageProxy
{
private:
Image *_image;
public:
void Draw()
{
if ( _image == NULL)
{
_image = LoadImage(fileName);
}
_image.Draw();
}
};
~~~
不多写了,代理类会拥有一个它代理的类的对象,如Draw()中,只有在真正需要_image的时候才把它载入内存。
Chain Of Responsibility(责任链)模式:该模式的目的是使多个对象有机会处理请求,如果一个对象不对请求作出响应,则沿着链表传递请求,直到找到可以处理该请求的对象为止。MFC中的消息处理机制就是如此实现的,有兴趣的可以找来看看。
Interpreter(解释器)模式,其实就是Composite模式,《设计模式》书上有一段描述说Interpreter不是Composite模式,但我没搞明白究竟在说什么(不知道是不是翻译的变了味)。反正它和Composite模式形式上没有太多区别。
Mediator(中介者)模式,感觉不容易简单讲明白,还是自己看书吧。事实上也不难理解。
Memento(备忘录)模式,这个模式没什么好讲的,就是在操作过程中,一个对象的状态可能改变,为了支持返回操作,可能需要把该对象的状态记录下来,这样的话就需要提供一个Memento类了。
Template Method(摸版方法),该模式主要用在框架中,如MFC中。方法的就是在基类中定义一个操作算法的骨架,而将一些函数延迟到子类中才实现。例子还是看书吧,其实实现方法和工厂方法类似。
参考书目:
1, 设计模式——可复用面向对象软件的基础(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英军等译 机械工业出版社
2, Head First Design Patterns(影印版)Freeman等著 东南大学出版社
3, 道法自然——面向对象实践指南 王咏武 王咏刚著 电子工业出版社
乱砍设计模式之十
最后更新于:2022-04-01 14:37:04
COMMAND 模式——诸葛亮造木牛流马
junguo
Command模式,中文名称是命令模式。该模式的目的是将不同的请求封装成不同的对象,这样可以用来做请求队列,请求日志,以及撤销的操作。该模式的核心是把请求封装成对象,这里的请求有些不好理解,我们还是看完例子后再说这个。先看例子。
这次没找到太好的例子,只好把木牛流马改造一下来说明我们的例子。《三国演义》上介绍诸葛亮造过这样的东西,相当于一个永动机了,理论上是完全不可能的事情。不过诸葛亮是神仙,老罗说他有这种能耐就算有吧。我们来看看要实现的功能,假设我们在屏幕上画出一头牛出来,用鼠标点一下它的嘴,可以喷火出来,再点一下,可以停止;点一下它的眼睛,可以放光,再点一下停止;点一下它的角,持续做顶人状,再点一下停止。(这段写的有些啰嗦,大家理解意思就好了。写到这里,再看,自己描述的是牛魔王的形象,呵呵,不过标题不改了)点击牛的其它部位也会发生其它相应的动作。
考虑一下,如何去做这些点击动作(这里只考虑这些点击动作的实现)。一般来说,结构化的方式,会为各个动作写一个函数,在这些函数里实现这些功能。如果我们的程序不需要扩展,这样写,其实也无所谓。来想想,如果我们需要为所有这些操作加上撤销操作该怎么办呢?我现在维护的系统是这样做的,新添加一个专门维护撤销操作的类,然后把每次操作的内容生成一个该类的对象,保存起来。这样一来,当对一个操作进行修改的时候,就需要对两个类进行处理。是否可以把这样的操作放到一个类中呢?这样修改起来更方便一些。我们可以用Command来实现这样的功能,也就是说把所有的操作都通过继承Command来实现。我不知道如何能说的更好一些。还是看例子吧。
~~~
//提炼一个Command接口
class Command
{
public:
virtual ~Command(){}
virtual void Execute() = 0;
protected:
Command(){}
};
class LightOnCommand : public Command
{
public:
void Execute()
{
//一般来说这里多是借助其它对象来完成,这个例子从简了
cout << "开始发光" << endl;
}
};
class LightOffCommand : public Command
{
public:
void Execute()
{
cout << "停止发光" << endl;
}
};
class FireOnCommand : public Command
{
public:
void Execute()
{
cout << "开始喷火" << endl;
}
};
class FireOffCommand : public Command
{
public:
void Execute()
{
cout << "结束喷火" << endl;
}
};
~~~
有了命令的代码,我们再来看看该如何组织这样的代码。
~~~
//公牛类
class Bulls
{
private:
//Command数组
vector m_Commands;
public:
Bulls()
{
m_Commands.push_back(new LightOnCommand());
m_Commands.push_back(new LightOffCommand());
m_Commands.push_back(new FireOnCommand());
m_Commands.push_back(new FireOffCommand());
}
//点击张开嘴
void ClickMouthOpen()
{
m_Commands[0]->Execute();
}
void ClickMouthClose()
{
m_Commands[1]->Execute();
}
void ClickEyeOpen()
{
m_Commands[2]->Execute();
}
void ClickEyeClose()
{
m_Commands[3]->Execute();
}
};
~~~
这里通过vecotr把需要的Command组织了起来,然后点击张开嘴等操作的时候,通过调用相应的Command来实现。其实到现在,也没看到Command的好处。设想一下,我们需要为操作提供一个撤消功能。该如何实现?没有Command你也许需要再提供一个类来完成该功能,有了Command,我们可以通过以下的方式实现:
~~~
//提炼一个Command接口
class Command
{
public:
virtual ~Command(){}
virtual void Execute() = 0;
//撤消操作
virtual void UnExecute() = 0;
protected:
Command(){}
};
class LightOnCommand : public Command
{
public:
void Execute()
{
//一般来说这里多是借助其它对象来完成,这个例子从简了
cout << "开始发光" << endl;
}
void UnExecute()
{
cout << "撤销发光操作" << endl;
}
};
~~~
在Command中增加一个撤消操作UnExecute,所有的子类实现该功能就可以了。我们再来看看撤消操作的组织。
~~~
//公牛类
class Bulls
{
private:
vector m_Commands;
//添加一个堆栈作为撤消操作的容器
stack m_UndoCommands;
public:
Bulls()
{
m_Commands.push_back(new LightOnCommand());
m_Commands.push_back(new LightOffCommand());
m_Commands.push_back(new FireOnCommand());
m_Commands.push_back(new FireOffCommand());
}
void ClickMouthOpen()
{
m_Commands[0]->Execute();
//每次操作后,把该操作放入堆栈中
m_UndoCommands.push(m_Commands[0]);
}
void ClickMouthClose()
{
m_Commands[1]->Execute();
m_UndoCommands.push(m_Commands[1]);
}
void ClickEyeOpen()
{
m_Commands[2]->Execute();
m_UndoCommands.push(m_Commands[2]);
}
void ClickEyeClose()
{
m_Commands[3]->Execute();
m_UndoCommands.push(m_Commands[3]);
}
void Undo()
{
//撤消通过调用m_UndoCommands来完成
if ( !m_UndoCommands.empty())
{
m_UndoCommands.top()->UnExecute();
m_UndoCommands.pop();
}
}
};
~~~
这里还是比较简单,其实这里你可以再把Redo操作加进去。再看看它的调用:
~~~
int main(int argc, char* argv[])
{
Bulls bulls;
bulls.ClickMouthOpen();
bulls.ClickEyeOpen();
bulls.ClickMouthClose();
bulls.ClickEyeClose();
bulls.Undo();
bulls.Undo();
bulls.Undo();
return 0;
}
~~~
Command是经常要用到的一个模式,一般支持撤消重做的地方都可以用到它。一般的文本,图形编辑器中都能看到它的影子。理解起来其实并不难,关键是如何去用了。
参考书目:
1, 设计模式——可复用面向对象软件的基础(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英军等译 机械工业出版社
2, Head First Design Patterns(影印版)Freeman等著 东南大学出版社
3, 道法自然——面向对象实践指南 王咏武 王咏刚著 电子工业出版社
乱砍设计模式之九
最后更新于:2022-04-01 14:37:02
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, 史记 网上找到的电子档
乱砍设计模式之八
最后更新于:2022-04-01 14:36:59
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, 人间词话手稿本 王国维 著 吴洋注释 内蒙古出版社
乱砍设计模式之七
最后更新于:2022-04-01 14:36:57
VISITOR模式 —— 齐天大圣闹天宫
junguo
Visitor模式的中文名称是访问者模式,该模式的目的是提供一个类来操作其它类型中的对象结构中的元素(也就是专门帮助其它类来实现原本属于它的函数)。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。是不是不明白这段话的意思?没关系,还是通过例子来理解该模式。我们先来简述一下例子。
呵呵,好不容易想到这么个土的掉渣的例子。别见怪,我实在想不出更好的例子。例子的背景大家应该都非常熟悉,在这儿就不扯淡了。简要描述一下我们要实现的功能。大家都知道在大闹天宫中,有二郎神和孙悟空打斗的情节。他们两个都有七十二变,七十二变在我们的例子里相当于七十二个方法。但他们的变化并不相同,如孙悟空变成庙的时候,尾巴变不掉,会变成一个旗杆;而二郎神没有尾巴。所以这里把他们各自封装。帮它们各自提供一个类。妖怪类Sprite和神仙类God看以下的类图。
好了,有了类图,开始开发。此次要实现的功能主要是帮助这两个类来实现它的七十二个方法。我们知道程序肯定不是一次写完的,每填加几个函数,我们就想进行一下单元测试,看看自己的代码有没有错误。这时候,我们就需要重新编译程序。由于这两个类的方法比较多,这样每填加一个函数,就可能需要把这整个类文件都重新编译一次。这是件耗费时间的事,你不想看到。那么有没有办法帮我们解决该问题呢?有的,就是现在提到的Visitor模式。我们可以把Sprite和God的所有操作提炼成一个一个单独的类,在这些类中完成原本属于它们的方法。怎么做呢?先来看看类图。
在类图中,你可以看到我们提炼了一个新类Visitor,它有两个子类Change1Vistor和Change2Vistor(如果有其它方法的话,我们可以添加新的类)。Visitor就是我们所说的访问者类了,就是要通过它来帮助我们把所有的方法都提炼到单独的类中。而Change1Vistor和Change2Vistor就是我们所要的具体类,用它来帮助我们实现神仙和妖怪的变化。我们说过了它们可能有七十二种变化,那么我们再填加新的变化的时候,就不需要去修改Sprite和God类的内容了。(当然了,如果真填加七十二中变化的话,这代码也够受的,估摸也好不到哪儿去,真有这样的系统,你可能需要去寻找其它方法了。)
我们再看看我们的Sprite和God,它们拥有共同的基类SuperMan,它只有一个虚拟函数 Accept(Visitor &) ,就是通过它来实现对Visitor类的调用了。Sprite和God类各自实现该方法。为了体现Visitor的真正意义,我们给Sprite和God各自添加了成员变量,其实Visitor的目的主要是帮助处理类中的数据成员了。在后面我们将讲述这个问题。好了,还是先来看看具体的代码,所先来看Visitor的代码:
~~~
// Visitor基类
class Visitor
{
public:
//抽象出来针对于Sprite对象的方法
virtual void VisitorSprite(Sprite *p) = 0;
//抽象出来针对God对象的方法
virtual void VisitorGod(God *p) = 0;
protected:
Visitor(){}
};
//针对于SuperMan的第一个操作
class Change1Vistor : public Visitor
{
public:
void VisitorSprite(Sprite *p)
{
cout << "这是妖怪 " << p->GetName() << " 的变化1" << endl;
};
void VisitorGod(God *p)
{
cout << "这是神仙 " << p->GetType() <<" 的变化1" << endl;
};
};
//针对于SuperMan的第一个操作
class Change2Vistor : public Visitor
{
public:
void VisitorSprite(Sprite *p)
{
cout << "这是妖怪的变化2" << endl;
};
void VisitorGod(God *p)
{
cout << "这是神仙的变化2" << endl;
};
};
~~~
这样,当我们有新的操作需要的时候,我们就可以重新生成一个类Change3Visitor,Change4Visitor等等,只要它们都继承于Visitor就可以了。这样你可以产生新的文件,而无需重新编译以前的类文件了。
我们再来看一下SuperMan的实现:
~~~
//超人类
class SuperMan
{
public:
virtual ~SuperMan(){}
//抽象出来的调用方法的接口
virtual void Accept(Visitor &) = 0;
protected:
SuperMan(){}
};
//妖怪类
class Sprite : public SuperMan
{
private:
string m_strName;
public:
Sprite(string strName):m_strName(strName){}
string GetName(){return m_strName;}
void Accept(Visitor &v);
};
//神仙类
class God : public SuperMan
{
private:
string m_strType;
public:
God(string strType):m_strType(strType){}
string GetType(){return m_strType;}
void Accept(Visitor &v);
};
//Sprite类的Accept实现
void Sprite::Accept(Visitor &v)
{
v.VisitorSprite(this);
}
//God类的Accept实现
void God::Accept(Visitor &v)
{
v.VisitorGod(this);
}
~~~
此处,我们需要注意的问题就是Accept的具体实现了。Sprite和God类需要分别调用针对于自己的接口函数。这里我们还应该考虑到一个问题,就是SuperMan的子类应该相对固定,不应该太多的变动。当它增加一个子类的时候,Visitor接口就需要变化,而相应的Visitor的所有子类也需要进行相应的变化。那么这样与该模式的初衷节省编译时间就背道而驰了,可能要花费更多的编译时间。
再来看看它的使用方法:
~~~
int main(int argc, char* argv[])
{
Sprite sp("孙悟空");
God g("天神,不是地府之神");
//变化1
Change1Vistor c1;
sp.Accept(c1);
g.Accept(c1);
//变化2
Change2Vistor c2;
sp.Accept(c2);
g.Accept(c2);
return 0;
}
~~~
我们可以看到Sprite和God对象调用相应函数的方法都可以通过Accept来实现。我们这里的实现比较简单,只是生成了一个Sprite和一个God类,而实际应用中它可能是一个列表,数组或者是一个组合(Composite),不过原理一致。其它方式大不过就是需要遍历所有的元素,并调用该方法。
再简单介绍一下《设计模式》中对该模式提供的例子。在编译器的实现过程中,会将所有源程序组合成一个语法树。该语法树中包括变量,赋值语句,判断语句等内容,这些内容都是一个单独的类,而该些类有一个统一的基类。这些类通过Composite模式组织成一个结构,也就是语法树。
在这样的语法树上,可能有这样一些操作:类型检查,代码优化等等,可能还有牵涉打印等,也就是操作是在不断变化的。而树的内容相对来说是比较固定的。这样的话,使用Visitor就可以把这些操作独立出来。使新的操作不至于影响原有的类。也不会使单个的类变的臃肿。
好了,这就是这次所要说的Visitor模式了。我们可以看到Visitor的初衷是为了节省编译时间也产生的。所以也可以这么说:设计模式是开发经验的总结,学习它的目的也就是能帮入门者更快的达到真正理解面向对象的水平。
参考书目:
1, 设计模式——可复用面向对象软件的基础(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英军等译 机械工业出版社
2, Head First Design Patterns(影印版)Freeman等著 东南大学出版社
3, 道法自然——面向对象实践指南 王咏武 王咏刚著 电子工业出版社
乱砍设计模式之六
最后更新于:2022-04-01 14:36:55
OBSERVER 及 ITERATOR 模式 —— 知我者谓我心忧,不知我者谓我何求
junguo
Observer模式的中文译名是观察者模式,定义是:定义对象间一种一对多的关系,当一个对象的状态发生变化时,所有依赖它的对象都得到通知并被自动更新。COM中的连接点事实上就是一种观察者模式,COM中的连接点主要是为过程化语言提供的,如果我们使用C++调用COM组件,那么我们可以直接利用回调函数的形式来完成同样的功能。而事实上Observer模式的具体实现,也是通过回调函数来完成。我们还是通过例子来学习该模式。
彼黍离离,彼稷之苗。行迈靡靡,中心摇摇。知我者,谓我心忧;不知我者,谓我何求。悠悠苍天,此何人哉!
彼黍离离,彼稷之穗。行迈靡靡,中心如醉。知我者,谓我心忧;不知我者,谓我何求。悠悠苍天,此何人哉!
彼黍离离,彼稷之实。行迈靡靡,中心如噎。知我者,谓我心忧;不知我者,谓我何求。悠悠苍天,此何人哉!
是从冯晓刚的《天下无贼》开始熟悉这首诗的。这首诗取自《诗经》中的《王风》,诗歌产生背景是这样的。西周王朝的最后一位君主周幽王,是个不成器的家伙。宠爱的妃子褒姒从来不笑,为了博美人一笑,他玩了一个“烽火戏诸侯”的游戏。结果等犬戎阵来攻击他的时候,烽火已经无法招来诸侯的救军。他被犬戎追杀,最后葬身于骊山脚下。西周的历史结束,继任的周平王被迫东迁,失去了多诸侯的控制权。而后一位当时周王朝的老臣,再回西周旧都的时候,已失往日的繁华,满目疮痍,感伤之下,作了此诗。不过我们的例子和此诗的关系不大,我只是觉得“知我者,谓我心忧”能很好的反映Observer模式的意义。
人总是把自己的心境和周边联系起来,心情不好了,天空就不再是蓝色的,花草也不再美丽。事实上什么也没有变,变的只是你的心情。不过在虚拟世界里,让世界随着自己的心情来变,就不是困难的事情了。我们来模式这样一个过程,当一个人的心情发生变化的时候,他所处于的天空,身边的花木都产生相应的变化。我们来看看类图:
从类图可以看到,我们定义了一个观察者接口,这个接口比较简单。只有一个虚函数Update。而它的子类Sky和Flower也很简单,就是实现Update。我们在看另一个接口Subject,它拥有一个Observer型的列表,用来存放所有的观察者。它的方法包括Attach添加一个观察者,Detach删除一个观察者,Notify是用来遍历所有的观察者,并调用它们的接口函数Update。Person是一个具体的类,事实上Subject的所有功能,都可以由它来实现。但我们应该记得一个规则:针对接口编程,而不要针对实现编程。这样有助于我们程序的扩展。
我们来看看代码,首先我们需要定义一个Observer的接口,而天空花木都继承于它,代码如下:
~~~
#ifndef __OBSERVER__H
#define __OBSERVER__H
//observer 接口,继承者需要实现Update方法
class Observer
{
public:
virtual ~Observer(){}
virtual void Update(Subject* pChange) = 0;
protected:
Observer(){}
};
//Sky类
class Sky : public Observer
{
private:
//具体的目标对象指针,是通过它的状态来改变观察者的行为
//可以有多个具体的目标对象,不过这里我们提供一个
Person* m_pPerson;
public:
Sky(Person *pPerson)
{
m_pPerson = pPerson;
m_pPerson->Attach(this);
}
virtual ~Sky()
{
m_pPerson->Detach(this);
}
void Update(Subject *pChange)
{
//由于可能有多个,所以这里需要判断
if ( pChange == m_pPerson )
{
if ( m_pPerson->GetSpirit() == true )
{
cout << "天空是蓝色的!" << endl;
}
else
{
cout << "乌云密布!" << endl;
}
}
}
};
//Flower类
class Flower : public Observer
{
private:
Person* m_pPerson;
public:
Flower(Person *pPerson)
{
m_pPerson = pPerson;
m_pPerson->Attach(this);
}
virtual ~Flower()
{
m_pPerson->Detach(this);
}
void Update(Subject *pChange)
{
if ( pChange == m_pPerson )
{
if ( m_pPerson->GetSpirit() == true )
{
cout << "百花灿烂时!" << endl;
}
else
{
cout << "凋零的玫瑰!" << endl;
}
}
}
};
#endif
~~~
以上为Observer.h头文件的内容,就是定义一个Observer接口,而后由具体的观察者类来继承。我们接着看看目标类的实现。
~~~
Subject.h文件的内容:
#ifndef __SUBJECT_HEADER__H
#define __SUBJECT_HEADER__H
//由于类Observer和Subject需要嵌套调用,所以不能直接#include"observer.h"
//所以需要先申明一下
class Observer;
//目标接口
class Subject
{
public:
//添加一个观察者
virtual void Attach(Observer *);
//删除一个观察者
virtual void Detach(Observer *);
//通知观察者
virtual void Notify();
protected:
Subject(){}
private:
//观察者列表
list m_ObserverList;
};
//具体的目标类
class Person : public Subject
{
private:
bool m_bGoodSpirit;
public:
Person():m_bGoodSpirit(true){}
bool GetSpirit(){return m_bGoodSpirit;}
void GoodSpirit()
{
m_bGoodSpirit = true;
cout << "我的心情很好,所以我看到了:" << endl;
Notify();
}
void BadSpirit()
{
m_bGoodSpirit = false;
cout << "我的心情很不好,所以我看到了:" << endl;
Notify();
}
};
#endif
~~~
这段代码就是定义了一个目标类的接口。当然你也可以不使用接口,直接定义一个Person类来实现该功能,但这样的话你在Observer的具体类的对象中就无法观察多种对象了。
(由于此次的代码中有嵌套使用类的情况,所以我标出了代码所在的文件名。不熟悉该用法的话,你可以注意一下。)
我们再来看看Subject类的具体实现:
以下是Subject.cpp的文件。
~~~
#include "Subject.h"
#include "Observer.h"
void Subject::Attach(Observer *pObserver)
{
m_ObserverList.push_back(pObserver);
}
void Subject::Detach(Observer *pObserver)
{
m_ObserverList.remove(pObserver);
}
void Subject::Notify()
{
list::iterator ite;
//遍历所有的观察者就行通知
for( ite = m_ObserverList.begin() ; ite != m_ObserverList.end() ;++ite)
(*ite)->Update(this);
}
~~~
由于嵌套使用的缘故,所以在Subject.h中无法直接使用Observer的对象,必须放到cpp中来实现,所以我们分割开来。这边的实现其实也很简单。我们再看看具体的使用。
~~~
#include "Observer.h"
#include "Subject.h"
int main(int argc, char* argv[])
{
Person p;
Sky sky(&p);
Flower flower(&p);
p.GoodSpirit();
p.BadSpirit();
return 0;
}
~~~
运行结果如下:
在我们的例子,Observer的列表是通过C++标准库的list来管理的,但为了讲解我们的下一个模式Iterator,我们将把它变为一个数组。我们还是先来看看Iterator的定义:提供一种方法顺序访问一个聚合对象中的各个元素,而不是暴露该对象的内部表示。其实Iterator就是为我们的数组,线性表一类的容器提供一个遍历的方法。但如今C++的标准库中已经很好的实现了这一功能,我们应该更多的是用标准库。在.net平台和Java的库中一般也都提供了很好的容器类,也都提供了迭代器。一般情况下,我们已经无需自己去实现迭代器。不过还是来简单看看的具体实现。为了便于扩展,我们还是提供一个Iterator的接口类:
~~~
template<class Item>
class Iterator
{
public:
virtual bool hasNext() = 0;
virtual Item Next() = 0;
};
~~~
这里提供的接口比较简单,只是判断是否有下一个元素和返回下一个元素的操作。一般的迭代器会提供重载加号,等于号等操作,这里省略了。我对STL也不太熟悉,下一阶段,准备学习一下,有时间的话,再和大家分享。
我们来实现一个数组的迭代器:
~~~
template<class Item>
class ArrayIterator : public Iterator
{
private:
//指向数组的指针
Item *m_pItems;
//当前迭代器的位置
int m_iPosition;
//数组的最大长度
int m_iMaxSize;
public:
//初始化函数
ArrayIterator(Item *pItems,int iMaxSize):
m_pItems(pItems),
m_iPosition(0),
m_iMaxSize(iMaxSize)
{
}
bool hasNext()
{
//判断当前的元素是否有效
if ( m_iPosition < m_iMaxSize && m_pItems[m_iPosition] != NULL )
{
return true;
}
else
{
return false;
}
}
Item Next()
{
//返回当前的元素,并将位置加一
return m_pItems[m_iPosition++];
}
};
~~~
也没有什么复杂的地方。我们再来看看Subject类的实现:
~~~
//目标接口
class Subject
{
public:
//添加一个观察者
virtual void Attach(Observer *);
//删除一个观察者
virtual void Detach(Observer *);
//通知观察者
virtual void Notify();
//创建迭代器
Iterator<Observer*> *CreateIterator()
{
return new ArrayIterator<Observer*>(m_ObserverList,10);
}
protected:
Subject():m_iPosition(0)
{
for(int i = 0; i < 10 ; i++ )
m_ObserverList[i] = NULL;
}
private:
int m_iPosition;
//观察者列表
Observer* m_ObserverList[10];
};
~~~
可以看到现在的Observer列表被定义成了Observer* m_ObserverList[10]。相应的我们帮它增加一个函数:
~~~
//创建迭代器
Iterator<Observer*> *CreateIterator()
{
return new ArrayIterator<Observer*>(m_ObserverList,10);
}
~~~
使用该函数来帮助我们生成一个迭代器。相应的我们以前用到遍历的函数Notify就需要进行相应的修改:
~~~
void Subject::Notify()
{
//遍历所有的观察者就行通知
Iterator<Observer*> *pObserver = CreateIterator();
while( pObserver->hasNext() )
{
pObserver->Next()->Update(this);
}
delete pObserver;
}
~~~
这里就是我们所要介绍的迭代器了。在STL中,迭代器有很好的解决方案,可以通过候捷的《STL源码剖析》来学习。我也正准备看这本书。
好了,这讲先到这儿。下次我们接着聊聊Visitor模式。
参考书目:
1, 设计模式——可复用面向对象软件的基础(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英军等译 机械工业出版社
2, Head First Design Patterns(影印版)Freeman等著 东南大学出版社
3, 道法自然——面向对象实践指南 王咏武 王咏刚著 电子工业出版社
4, 原来诗经可以这么读 唐文著 河北教育出版社
乱砍设计模式之五
最后更新于:2022-04-01 14:36:53
FACTORY 与 ABSTRACT FACTORY模式 —— 号令秦姬驱赵女,艳李秾桃临战场
junguo
这一次,将集中讲一下创建型模式,主要以Factory和Abstract Factory模式为主。按上次的惯例,还是以例子开始。这次的例子仍以战场和美女为例,呵呵,和战场及美女死磕上了。采用这样的例子,只是想帮助大家更好的记忆,我最简化自己的例子,以帮助大家认识模式之形。写完这个系列,也许会尝试写一些具体实例的模式。不过这样的书其实也有,《道法自然》就是一本优秀的书,有兴趣可以找来看看。还是来看我们要用到的例子:
恒王好武兼好色,遂教美女习骑射。
秾歌艳舞不成欢,列阵挽戈为自得。
眼前不见尘沙起,将军倩影红灯里。
叱咤时闻口舌香,霜矛雪剑娇难举。
丁香结子芙蓉绦,不系明珠系宝刀。
战罢夜阑心力怯,脂痕粉渍污鲛鮹。
明年流寇走山东,强吞虎豹势如蜂。
王率天兵思剿灭,一战再战不成功。
腥风吹折陇头麦,日照旌旗虎帐空。
青山寂寂水澌澌,正是恒王战死时。
雨淋白骨血染草,月冷黄沙鬼守尸。
纷纷将士只保身,青州眼见皆灰尘。
不期忠义明闺阁,愤起恒王得意人。
恒王得意数谁行,姽婳将军林四娘。
号令秦姬驱赵女,艳李秾桃临战场。
秀鞍有泪春愁重,铁甲无声夜气凉。
胜负自然难预定,誓盟生死报前王。
贼势猖獗不可敌,柳折花残实可伤。
魂依城郭家乡近,马践胭脂骨髓香。
星驰时报入京师,谁家儿女不伤悲。
天子惊慌恨失守,此时文武皆垂首。
何事文武立朝纲,不及闺中林四娘。
我为四娘长叹息,歌成余意尚彷徨。
这首诗取自《红楼梦》第七十八回,该诗的起因如下。贾政和他的幕僚聊天的时候讲到一个故事。当年有一个哥们被封为恒王,镇守青州,此君好色成性,闲暇的时候还好弄武摆阵。自己玩的不过瘾,把他选来的美女也拉了进来。每天看着这帮美女舞刀弄枪,还时常进行一些军事演习。在众多美女中有一个叫林四娘的,最为漂亮,而且武功最好,恒王让她做了众人的统领,并号为姽婳将军。不想第二年,青州发生了农民起义,此君没有把农民军当回事,确把自己当回事。亲自带兵去征剿,结果打了两战,输了两战,损兵折将,还将自己搭了进去,成了烈士。消息传到青州城中,马上乱了套。用指头都可以想象得到,这样的一个王,手下能有什么人才?众多文武官员,不想着退敌护城,有的想着如何逃跑,有的想着不如献城。这样的情形激怒了林四娘,她集合起了所有女将,对众将言到:“你我皆向蒙王恩,戴天履地,不能报其万一。今王既殒身国事,我意亦当殒身于王。尔等有愿随者,即时同我前往;有不愿者,亦早各散。”结果没有一个人愿意离开,林四娘带人连夜出城杀向贼营里面,刚开始人家没防备,被她们杀了几个人。后来一看是都是女人,就一起围了上来,把众女将杀的一个不剩。女为悦已者容变成了女为悦已者死,我在想为了这么一个王,她们死的值吗?这帮农民起义军也真能下的去手,不过想想好多事情都不可理喻。当年八路军对日本鬼子采取了优待俘虏的政策。对自己人确毫不含糊,反拖肃反的时候,很多青年男女被秘密处决。为了节省子弹,还研发了很多土制方法,如直接把人填到井里(《白鹿原》里的共产党员白玲就是这么死的),还有在人身上通上较弱的电流慢慢折磨至死(看过《绿色奇迹》没有?看过的话,回想一下执行电刑时由于没有往海绵中加水而被烧焦的哪个犯人的死状,你就能想象到当时的惨烈程度了)。所以我记住了我们《马哲》老师的一句话:千万不要去沾什么主义。接着上面的故事,后来消息报了上去,朝廷重新派人来镇压农民起义,这次来人不象恒王那样无能,剿灭了这帮农民起义军。
众幕僚听着直流哈喇子,羡慕恒王羡慕的要死。文人的酸劲上来要做首诗来应和。贾政把宝玉,贾环和贾兰也叫了过来,让他们也一人做一首。宝玉写下了这首古体长诗《姽婳词》。《红楼梦》是一本伟大的作品,将中国的诗书琴画,饮食建筑尽融其中,每个人在其中都可以找到自己感兴趣的部分。
你应该猜到了,我们此次的例子是女将军的战争场面。战争一般说来肯定离不开男人,所以男性将军也需要保留。但女将军的作战处理可能和男性并不相同,所以我们会她们单独的提供一个类,而女将军和男将军会有一个共同的接口。先来看类图:
假设在没有继承时有这么一个函数:
~~~
void War()
{
General *pGeneral = new General();
pGeneral->Ready();
pGeneral->Advance();
pGeneral->Assault();
delete pGeneral;
}
~~~
在有了继承的情况下,该函数就可能会变成以下的情况:
~~~
void War(string strType)
{
General *pGeneral ;
if ( strType == “GirlGeneral”)
{
pGeneral = new GirlGeneral();
}
else if ( strType == “ManGeneral”)
{
pGeneral = new ManGeneral();
}
pGeneral->Ready();
pGeneral->Advance();
pGeneral->Assault();
delete pGeneral;
}
~~~
想象一下,创建对象的地方很可能并不只一处。那么你就可能在很多地方看到类似的代码,想象一下如果我们再增加一些少年将军,神仙将军一类的基类,那你是不是需要一个一个找到这样的函数去改?(好多时候遇到这样的情况,开发人员怪用户提的要求变态,确很少考虑自己的程序扩展性不强。)为了避免这种情况,最简单的方法就是将类的创建过程独立为一个单独的类,如下:
~~~
class GeneralFactory
{
public:
static General *CreateGeneral(string strType)
{
if ( strType == “GirlGeneral”)
{
pGeneral = new GirlGeneral();
}
else if ( strType == “ManGeneral”)
{
pGeneral = new ManGeneral();
}
}
};
~~~
这样我们的创建代码被放到了一个统一的地方,在类进行扩展的时候,就可以方便的进行修改了。也避免了代码的冗余。不过这个方法并没有归于GOF的设计模式中,我们以下将谈Factory模式。
再考虑一个问题,我们的War是出现在一个类中,如我们有一个军队类,而军队分为男军和女军。男军的将领肯定是男将军,而女军的将领肯定是女将军。而且General类只会在军队类中用到。那我们还有没有必要写一个创建类呢?答案是否定的。我们先来看类图:
那么这时候,我们就可以将CreateGeneral放到Army类中,而通过它的子类来实现它。好的,我们还是先看看具体的代码,这样有助于你理解。
~~~
#include
#include
using namespace std;
class General
{
public:
virtual void Ready() = 0;
virtual void Advance() = 0;
virtual void Assault() = 0;
};
class GirlGeneral : public General
{
private:
string m_strName;
public:
GirlGeneral(string strName):m_strName(strName){}
void Ready()
{
cout << " 女将军 " << m_strName << "正在准备!"<< endl;
}
void Advance()
{
cout << " 女将军 " << m_strName << "正在前进!"<< endl;
}
void Assault()
{
cout << " 女将军 " << m_strName << "正在攻击!"<< endl;
}
};
class ManGeneral : public General
{
private:
string m_strName;
public:
ManGeneral(string strName):m_strName(strName){}
void Ready()
{
cout << " 将军 " << m_strName << "正在准备!"<< endl;
}
void Advance()
{
cout << " 将军 " << m_strName << "正在前进!"<< endl;
}
void Assault()
{
cout << " 将军 " << m_strName << "正在攻击!"<< endl;
}
};
class Army
{
public:
void war(string strName)
{
cout << "战斗步骤" << endl;
//此处调用虚函数,事实上调用的是子类的方法
General *pGeneral = CreateGeneral(strName);
pGeneral->Ready();
pGeneral->Advance();
pGeneral->Assault();
cout << endl;
}
//此处定义一个创建Geraral的类,而不具体实现
virtual General* CreateGeneral(string strName) = 0;
};
class GirlArmy : public Army
{
public:
General *CreateGeneral(string strName)
{
return new GirlGeneral(strName);
}
};
class ManArmy : public Army
{
public:
General *CreateGeneral(string strName)
{
return new ManGeneral(strName);
}
};
int main(int argc, char* argv[])
{
GirlArmy gArmy;
gArmy.war("姽婳将军");
ManArmy mArmy;
mArmy.war("笨蛋将军");
return 0;
}
~~~
代码比较简单,无需太多解释,需要注意的就是在Army类中注释过的地方就可以了。
下面我们来看GOF对于Factory模式的定义:定义一个用于创建对象的接口,让子类决定实例化哪个类。Factory Method使一个类的实例化延迟到其子类。
GOF书中提供的一个例子机具代表性,是Factory的典型用法,我们来看看。
这是我们在文档视中看到的典型做法,在MFC中也可以找到。Application并不是自己创建文档,而是通过在子类中重载CreateDocument来实现。Factory模式,我们的讲解先到此。
我们再来考虑一个问题:我们的Army类存在与General同样的问题,生成对象的时候需要考虑到它的类型。那我们是否需要象上面提到的GeneralFactory一样,为它也提供一个类厂呢?接着扩展我们的想象,由于女将的加入,她们使用的兵器,坐骑以至战争场景等内容也许都需要做相应的扩展,那么我们是否需要这么多类都提供一个类厂呢?我们可以注意到所有这些扩展的类都有相似的功能都是为女性提供的,那么我们就可以给它们提供一个统一的类来实现创建过程。该方式就是Abstract Factory模式,我们首先来看看该模式。
Abstract Factory模式的定义是:提供一个创建一系列产品或者相互依赖对象的接口,而无需调用处指定具体的类。接着看看Abstract Factory的类图:
图中AbstractFactory是一个抽象接口,定义了创建不同产品的虚拟函数。而ConcreteFactory1和ConcreteFactory2是我们用到的具体的生成产品的类,图上标的虚线表示它们具体要生成的那个类的对象。而AbstractProcutA和AbstractProcutB是抽象产品接口,相当于我们的Army和General接口。而ConcreteProcutA1和ConcreProcutB1是具体的产品类,我们的例子中相当于GirlArmy和GirlGeneral。而ConcreteProcutA2和ConcreProcutB2就相于ManArmy和ManGeneral了。
接着看我们的例子,此处就不画类图了。直接看看代码:
~~~
class AbstractFactory
{
public:
virtual General* CreateGeneral() = 0;
virtual Army* CreateArmy() = 0;
};
class GirlFactory : public AbstractFactory
{
public:
General *CreateGeneral(string strName)
{
return new GirlGeneral(strName);
}
Army *CreateArmy()
{
return new GirlArmy();
}
};
class ManFactory : public AbstractFactory
{
public:
General *CreateGeneral(string strName)
{
return new ManGeneral(strName);
}
Army *CreateArmy()
{
return new ManArmy();
}
};
~~~
这样在我们的例子中的函数:
~~~
void war(string strName)
{
cout << "战斗步骤" << endl;
General *pGeneral = CreateGeneral(strName);
pGeneral->Ready();
pGeneral->Advance();
pGeneral->Assault();
cout << endl;
}
~~~
可能变为
~~~
void war(AbstractFactory *pFactory,string strName)
{
cout << "战斗步骤" << endl;
General *pGeneral = AbstractFactory ->CreateGeneral(strName);
pGeneral->Ready();
pGeneral->Advance();
pGeneral->Assault();
cout << endl;
}
~~~
Abstract Factory在现实开发中用的比较多的情况是在处理数据库的时候,如果你的程序需要同时支持oracle和SQL Server数据库,那样几乎所有的数据操作类都需要两套代码。那么使用Abstract Factory模式,只需通过配置文件生成不同的创建函数,就可以实现不同数据库的实现了。
事实上Abstract Factory相当于Factory模式的扩展,都是基类定义一个创建对象的方法,而由子类来实现。
Abstract Factory模式也可以使用PROTOTYPE(原型)模式来实现,PROTOTYPE的定义是:用原型实例来创建对象的类。说白了就是通过拷贝构造函数来实现新类的创建。这里就不多讲了。
还有Builder模式,其实和Abstract Factory很相似,但它强调的内容不同,它强调的是一步一步要创建对象,也就是说它会帮助对象创建一些内容。而Abstract Factory是直接返回对象。
接着考虑,我们想把我们的工厂类作成一个单件类型,这样的话,由于工厂类只有一个对象,我们就容易对工厂对象所要创建的对象做些控制,如可以限定对象的生成数,控制对象的生成时机等。我们不能象上节一样直接把CreateGeneral定义成static型,因为这样就没有动态的特性。那用什么方法呢?还是看代码:
~~~
class GirlFactory : public AbstractFactory
{
private:
static GirlFactory *instance;
GirlFactory(){}
public:
static GirlFactory* GetInstance()
{
if ( instance == NULL )
{
instance = new GirlFactory();
}
return instance;
}
General *CreateGeneral(string strName)
{
return new GirlGeneral(strName);
}
Army *CreateArmy()
{
return new GirlArmy();
}
};
GirlFactory *GirlFactory::instance = NULL;
~~~
我们的类工厂中,构造函数GirlFactory被设置成了私有成员函数,这样只有它本身才能调用。我们再填加一个静态成员变量instance,这样保证了只有一个对象。而后再定义一个静态函数GetInstance,它返回所需要的类对象。
这样我们对类对象的调用就成了以下的形式:
GirlFactory *pGirlFactory = GirlFactory::GetInstance();
好了,这就是单件模式了。这次就先讲这么多了。
参考书目:
1,设计模式——可复用面向对象软件的基础(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英军等译 机械工业出版社
2,Head First Design Patterns(影印版)Freeman等著 东南大学出版社
3,道法自然——面向对象实践指南 王咏武 王咏刚著 电子工业出版社
4,红楼梦(脂批汇校本) —— 网上找到的电子档
乱砍设计模式之四
最后更新于:2022-04-01 14:36:50
COMPOSITE与BUILDER模式 —— 忠义堂石碣受天文 梁山泊英雄排座次
[junguo](#)
Composite模式的中文名字是组合模式,该模式的目的是使单个对象和它的对象组合(一般是数组或者链表的结构)拥有统一的操作方式,这样可以简化客户的使用。我们还是通过具体的例子来理解该模式。还是先来一段例子背景介绍:
话说,宋江带人攻陷东平东昌两郡,收降了双枪将董平和没羽箭张清,而后皇甫端来归。梁山聚集了武将,书生,医生以至兽医等各色人等,成为了一个成熟的社区。着点人数正好一百单八人,是个吉利数字,为此宋江组织了浩大的祭天仪式。在祭天仪式进行期间,发现了埋于地下的石碣,石碣上排定了三十六天罡,七十二地煞的座次。次序以定,梁山也开始走上正规,开始划分各位好汉的职责。依据各人特长,分别任职于马军,步军,军师等角色。有了规矩,才成方圆,自此梁山告别了单挑的时代,作战有了一些固定的组合。如李逵出战,必有项充,李衮护在身边;这位头脑简单的杀人魔王的典型特点是攻强守弱,而且不着盔甲,如若无人保护估摸早死了。还有另外一些组合:如矮脚虎王英和一丈青扈三娘这样的夫妻组合(扈三娘嫁给了王英,你能想通吗?我想不通);还有解宝解珍这样的兄弟组合等。
来考虑一下,如何实现这样的功能,武将可以单独出击,也可以成群的出击?该如何去实现这样的功能呢?还是一步一步来,先看类图:
这是我们建立的武将类,该类和我们第一讲《赵子龙单骑救主》中建立的武将类相同(为了突出中心功能,简化了功能)。考虑一下在这种情况下,我们如果想让多个武将一起出击,该如何实现?最单纯的做法就是在调用该类的地方生成一个数组,然后客户端通过该数组来调用各个对象的操作。大概代码如下:
~~~
General general[10];
for( int i = 0; i<10; i++ )
general[i].Assault();
~~~
客户端也可以将这些操作封装在不同的函数中,但如果武将类填加新的函数的时候,客户端也需要相应的增加此类德函数。这是典型的封装不好的例子,客户端需要增加特殊的函数来支持对象。我自己的工作中就遇到了这样的问题,类没有封装好,针对于这些类的操作,你不得不填加一些函数,接着就可以发现在不同的文件里有很多功能相似的函数(一个类往往在多处用到,而这些往往是由不同的人写的,这样很容易造成重复代码)。整个程序被改得乱码七糟的,改这样的东西真是头痛万分,更加上你如果要用到这些外部的函数的时候,都不知道用那个。那如何来解决该问题呢?找到组合模式,就找到了解决之道。先来看看组合模式的类图描述:
Component是为Leaf和Composite抽象出来的共同接口,可以通过它来统一操作单个对象或者组合对象。
Leaf是我们具体要用到的类,它用来完成具体的功能。Composite是我们用来组合对象的类,并实现对子对象的管理;它通过Add,Remove和GetChild来实现对子对象的管理。Composite中会含有一个Component型的对象列表childers,它也会重载Leaf中所需要的函数,然后通过遍历执行所有childer的函数。该childer也可以是Composite型的,这样就可以有一个递归的结构。
我们用Composite模式来重新设计我们的类图,如下:
理解模式最有效的方式,还是看具体的代码:
~~~
#include
#include
#include
#include
using namespace std;
//抽象部件类,是Leaf和Composite的共有接口
class GeneralComponent
{
public:
//为了确保调用到析构函数,此处声明为虚函数
virtual ~GeneralComponent(){}
//由于General中不实现Add,Remove,GetChild,此处需要一个缺省的实现,但此处抛出异常更合适一些,为了简单,省略了。
virtual void Add(GeneralComponent* pComonent) {}
virtual void Remove(GeneralComponent* pComonent) {}
virtual GeneralComponent* GetChild(int i) {return NULL;}
virtual void Assault() = 0;
};
//具体用到的类
class General : public GeneralComponent
{
private:
string m_strName;
public:
General(string strName):m_strName(strName){}
void Assault()
{
cout << m_strName << " 进入战斗!" << endl;
}
};
//组合类
class GeneralComposite : public GeneralComponent
{
private:
string m_strName;
//用来存放需要组合的对象
vector m_pComponents;
public:
GeneralComposite(string strName):m_strName(strName){}
virtual ~GeneralComposite()
{
vector::iterator pos;
for( pos = m_pComponents.begin();pos::iterator ivite=
find(m_pComponents.begin(),m_pComponents.end(),pGeneral);
m_pComponents.erase(ivite);
}
GeneralComponent* GetChild(int i)
{
return m_pComponents[i];
}
void Assault()
{
cout << m_strName << "战斗序列" << endl;
//需要调用所有的组合对象的操作
vector::iterator pos;
for( pos = m_pComponents.begin();posAssault();
}
}
};
~~~
我们再来看一下,客户端如何来调用该对象:
~~~
int main(int argc, char* argv[])
{
GeneralComposite pArmy("梁山大军");
GeneralComposite* pHorseArmy = new GeneralComposite("梁山马军");
GeneralComposite* pPaceArmy = new GeneralComposite("梁山步军");
General *pWusong = new General("好汉武松");
General *pHuaheshang = new General("侠客鲁智深");
General *pLida = new General("莽汉李逵");
General *pLinchong = new General("英雄林冲");
General *pGuansheng = new General("大刀关胜");
pHorseArmy->Add(pLinchong);
pHorseArmy->Add(pGuansheng);
pPaceArmy->Add(pWusong);
pPaceArmy->Add(pHuaheshang);
pPaceArmy->Add(pLida);
pArmy.Add(pHorseArmy);
pArmy.Add(pPaceArmy);
pArmy.Assault();
return 0;
}
~~~
运行结果如下:
我们可以看到,通过组合模式,对于对象的调用变得简单了起来,只要通过一个函数就可以实现对所有对象的统一调用。这样做最大的好处就是将所有的代码统一成了一份,可以避免在不同的地方出现类似的代码。在多人合作的情况下,由于交流不充分的问题,很多时候开发人员各自写各自的代码,而不关心别人的代码,极容易产生相同功能的代码。当我们的类进行扩展的时候,可能很多地方都需要进行修改。
我们再来看看组合模式可能存在的问题。Leaf类继承了Component中的所有方法,但对于Add,Remove,GetChild等操作,这些操作对它来说是无意义的。我们也可以把这些操作放到Composite中,但在这种情况下,我们将无法通过基类来直接操作Composite对象。个人觉得这个问题可以根据具体问题权衡解决。我们也可以为Component扩展更多的Leaf或者Composite,在这种情况下,如果我们想控制Composite中只能添加限定的Leaf或者Composite在静态情况下是不可能的,必须在运行时刻去判断。
还有,我们应该最大化Component接口,我们使用该接口的目的就是使用户不知道它具体操作的是哪个Leaf或者Composite,所以我们应该尽量多定义一些它们公共的操作。这样还是会造成我们上面提到的会给一些类带来无意义的功能。
我们再来简单比较一下Composite和Decorator模式,这两个模式都会有一个子类拥有基类的指针,不同之处是Composite会拥有多个组件,而Decorator只拥有一个组件。Composite主要目的是对象的聚集,使它们的调用统一化;而Decorator不是此目的,它的目的是给对象添加一些额外的功能。
好了,Composite的讲解先到此为止。如果你感兴趣的话,可以试着把我们第一章用到的策略模式应用到该处。不过我们下面看另外一个问题。在main中,我们需要很多代码去完成对象的创建。我们是否可以将对象的创建部分也做一些封装呢?我们可以找找创建型的模式中是否有适合我们的模式?
在《设计模式》中,我们可以找到Builder模式,它的定义是:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。和我们的意图有点象,先来看看《设计模式》中为我们提供的类图:
作一下解释:
1, Director是具体要用到复杂对象的部分(如GeneralComponent)的类(我们程序中是main中调用)。它会通过Builder来建造自己需要的对象,而不需要自己通过new来得到所要的对象。
2, 而Builder的是一个抽象的创建对象的接口。
3, ConcreteBuilder是我们用来具体创建对象的类。它可能有多个,用来帮我们建造不同类型的对象。如我们把我们的General派生出不同的风格,如先秦风格和罗马风格的时候,我们可能需要生成不同的类。而每次创建一种风格的对象,我们就可以扩展Builder而生成不同的创建类。
我们的例子比较简单,不需要这么多东西。Builder的重要特征是:它是用来一步一步创建复杂对象类型的。我们的组合类由于需要组合不同的组件,所以整个创建过程比较复杂,那么可以借用Builder模式的特点来创建我们的对象。好的,看看具体的代码:
~~~
class Builder
{
public:
static GeneralComponent* CreateCompise(GeneralComponent *pParent,string strArr[],int iLength = 1)
{
if ( pParent != NULL )
{
for( int i = 0 ; i < iLength ; i++ )
{
pParent->Add(new GeneralComposite(strArr[i]));
}
return pParent;
}
else
{
return new GeneralComposite(strArr[0]);
}
}
static GeneralComponent* CreateGeneral(GeneralComponent *pParent,string strArr[],int iLength = 1)
{
if ( pParent != NULL )
{
for( int i = 0 ; i < iLength ; i++ )
{
pParent->Add(new General(strArr[i]));
}
return pParent;
}
else
{
return new General(strArr[0]);
}
}
};
~~~
这边的代码略显简单,只重意了,你可以看到,创建过程被封装成CreateCompise和CreateGeneral两个方法,通过它们来创建我们所需要的对象。只要将所需要的Composite(为它创建Leaf和子Composite的),还有创建对象所需要的参数(例子有些简单,只要一个初始化参数)数组和数组长度传进函数就可以帮我们创建所需要的对象了。由于Builder没有成员变量,将两个函数设置成了静态函数,想当于一个单件类型。我们再看看它的具体用法:
~~~
int main(int argc, char* argv[])
{
string strArmy[1] = {"梁山大军"};
string strArmyChild[2] = {"梁山马军","梁山步军"};
string strSpecLeader[3] = {"好汉武松","侠客鲁智深","莽汉李逵"};
string strHorseLeader[2] = {"英雄林冲","大刀关胜"};
GeneralComponent *pArmy = Builder::CreateCompise(NULL,strArmy);
Builder::CreateCompise(pArmy,strArmyChild,2);
Builder::CreateGeneral(pArmy->GetChild(0),strHorseLeader,2);
Builder::CreateGeneral(pArmy->GetChild(1),strSpecLeader,3);
pArmy->Assault();
delete pArmy;
}
~~~
这样我们就可以统一对象的创建过程。做到一定程度的聚合。我个人感觉模式的学习过程是从形到意(就是从一些具体的例子看起,通过具体的例子来理解模式);而后是重意不重形(明白模式的意义之后,我们就可以随意使用模式了,而不需要硬套公式)。所以此处使用了和书上并不一致的Builder模型。其实Builder模式与Abstract Factory模式很相似,我们下次描述Abstract Factory模式的时候,再回头比较一下。
参考书目:
1, 设计模式——可复用面向对象软件的基础(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英军等译 机械工业出版社
2, Head First Design Patterns(影印版)Freeman等著 东南大学出版社
3, 道法自然——面向对象实践指南 王咏武 王咏刚著 电子工业出版社
4, 水浒传 —— 网上找到的电子档
乱砍设计模式之三
最后更新于:2022-04-01 14:36:47
DECORATOR模式———小轩窗,正梳妆
[junguo](#)
DECORATOR中文的意思是装饰,该模式的动机是帮助对象动态的添加一些功能。它强调是为对象而不是为类添加功能。为类添加功能最有效的方式是通过继承来实现,但继承的缺点是不够灵活。下面我们还是通过例子来理解该模式。
十年生死两茫茫,不思量,自难忘。
千里孤坟,无处话凄凉。
纵使相逢应不识,尘满面,鬓如霜。
夜来幽梦忽还乡,小轩窗,正梳妆。
相顾无言,惟有泪千行。
料的年年断肠处,明月夜,短松冈。
挺喜欢这首词的,是我能背下来的不多的几首词中的一首。这是苏东坡为亡妻王弗写的一首悼亡诗。王弗十六岁时嫁给苏轼,婚后夫妻恩爱。苏轼的《亡妻墓志铭》中写到:“见轼读书,则终日不去。”颇有“红袖添香夜伴读”之味。可惜正如苏轼的《明月几时有》中写的一样:“人有悲欢离合,月有阴晴圆缺,此事古难全。”王氏于二十七岁时病逝,苏轼悲痛万分。十年后的一个夜晚,苏轼又一次在梦中梦到了与妻子往日的缠绵,醒来后不禁泪下,写下了这首词。
前两天,和一兄弟网上聊天。他说自己陷入情网,无法自拔,痛苦万分。问他什么原因?他说自己爱上了一个女孩,但人家已经有了男朋友;看着年龄越来越大,找到自己合适的伴侣是越来越难。为了给他雪上加霜,我劝他死心吧,找到合适的概率太小;不如等到那天混的可以了,有房子后,发个征婚启示什么的,然后就可以直接结婚了。他坚决的拒绝了我给他的友好建议,说看到很多结婚的朋友,为了鸡毛蒜皮的小事而吵架,一点意思都没有;与其这样,还不如自己一个人。是啊,随着工业文明的进步,人的神经也被不断的拉紧,少了农耕时代的悠闲。我们的生活越来越匆忙,少了欣赏美的情趣,生活中的点点滴滴的美,有多少人可以体验得到?“小轩窗,正梳妆”,古人可以体会得到的美,而在我们的字典里能找到吗?为了些须小事而吵架分离,婚姻不再是“围城”,令城外的我们也忘而却步。很多人都在等待,等待那份不会因物质而庸俗化的爱情,能等到吗?“但愿人长久,千里共婵娟”,将此祝福天下所有期望这份爱情的人。
好了,我们来描述我们的例子吧。考虑一下美女梳妆的情景,她会盘弄自己的头发,填加首饰在自己头上,可能还有耳环一类的东西。但每个美女身上的饰物并不相同,我们为美女提供一个类的话,如何可以做到让她们的饰物各不相同呢?好的,我们还是一步一步来,首先抽象出一个美女类:
![](image/d41d8cd98f00b204e9800998ecf8427e.jpg)
这个类比较简单,就是画一个美女出来(不好意思,这里仍然是用文字意思一下,不熟悉图象处理;真希望能真正画一个出来,什么时候有时间好好学学)。我们再来看如何帮美女填加饰物,仍是先看传统的方式:
![](image/d41d8cd98f00b204e9800998ecf8427e.jpg)
那么需要填加这样的变量m_IsHasHair(什么,美女可以没有头发?当然可以,不信可以找金庸的《笑傲江湖》来看看),m_IsHasNeaklace(项链),m_IsEarbob(耳环)。那么我们的Draw的实现就需要根据不同的条件来实现,大概的代码如下:
~~~
Void Draw()
{
……画一个美女
If( m_IsHasHair)
{
…….
}
If ( m_IsHasNeakLace)
{
……..
}
If ( m_IsEarbob)
{
……..
}
}
~~~
还是结构化的东西,我们需要考虑的问题是:当新的饰物需要填加到类中的时候,我们该如何处理?再次强调面向对象的基本原则:一个模块对扩展应该是开放的,对修改应该是关闭的。如果按现在的做法,我们不得不再次打开Draw为它添加新的条件,实现新的功能。这个方案被否决,那么我优先考虑到的还是继承,看它能不能完成我们的功能?
如上类图,我们试图使用继承的方法来实现该功能。当新加装饰类型的时候,是可以做到不需改变以前的代码。但新的问题又来,当一个美女既有头发又有项链的时候,我们该怎么办。那么通过继承的方法就是新加一个类:BeautyWithHairAndNeakLace,如果一个一个条件组合下去,类又失去了控制,又是类爆炸现象。简单的继承无法完成我们的功能。还好,如果你知道了Decorator模式,问题就会变的简单起来。我们首先来看看Decorator模式的类图。
对该类图,做一些说明:
1, 我们需要为我们要用到的类和它所需要的包装类提供一个共同的接口:Component。
2, ConcreteComponent是我们要用到的类,就是需要为该类的对象动态的填加一些功能进去。
3, Decorator是我们要用到的包装类,它也应该是一个抽象类,我们是通过它的子类来实现对ConcreteComponent的包装。每个Decorator子类的实例都应该拥有一个指向Component的指针或引用。
4, ConcreteDecoratorA和ConcreteDecoratorB是我们用来包装的实类,它拥有一个指向Component的实例。我们可以在该类中填加新的类型和方法,但这些类型和方法只能在类内部使用,因为该类的调用是通过Component接口来实现的。
好了,看完该类图,你是否对该模式还是不清楚?没关系,看到实际的代码后,一切就清晰了。我们再按该模式来设计我们的美女类图:
帮我们的美女抽象出一个接口:BeautyInterface。Beauty继承自BeautyInterface是我们实际要用到的类。而BeautyDecorator是我们抽象出的装饰类。Hair,Necklace,Earbob是要具体用来装饰的类,可以看到他们重载了Draw,调用接口的Draw并且填加一些自己的功能进去。具体该如何用呢?来看代码。
~~~
//美女接口
class BeautyInterface
{
public:
virtual void Draw() = 0;
};
//美女实类
class Beauty : public BeautyInterface
{
public:
void Draw(){ cout << "This is a beauty!" << endl;}
};
//美女装饰类
class BeautyDecorator : public BeautyInterface
{
public:
virtual void Draw() = 0;
};
//头发类
class Hair : public BeautyDecorator
{
private:
BeautyInterface *m_pBeauty;
public:
Hair(BeautyInterface *pBeauty):m_pBeauty(pBeauty){}
void Draw()
{
m_pBeauty->Draw();
DrawHair();
}
private:
void DrawHair()
{
cout << "Hi,this is my beautiful hair!" << endl;
}
};
//项链类
class Neaklace : public BeautyDecorator
{
private:
BeautyInterface *m_pBeauty;
public:
Neaklace(BeautyInterface *pBeauty):m_pBeauty(pBeauty){}
void Draw()
{
m_pBeauty->Draw();
DrawNeaklace();
}
private:
void DrawNeaklace()
{
cout << "Hi,look at my neaklace!" << endl;
}
};
//耳环类
class Earbob : public BeautyDecorator
{
private:
BeautyInterface *m_pBeauty;
public:
Earbob(BeautyInterface *pBeauty):m_pBeauty(pBeauty){}
void Draw()
{
m_pBeauty->Draw();
DrawEarbob();
}
private:
void DrawEarbob()
{
cout << "Hi,look at my Earbob!" << endl;
}
};
~~~
好了,这就是我们要实现的类代码。我们需要注意的是Hair等装饰类的构造函数,需要初始化一个BeautyInterface 接口指针。
Hair(BeautyInterface *pBeauty):m_pBeauty(pBeauty){}
我们再来看看该类的具体应用:
~~~
int main(int argc, char* argv[])
{
BeautyInterface *pBeauty = new Earbob(new Neaklace(new Hair(new Beauty)));
pBeauty->Draw();
delete pBeauty;
return 0;
}
~~~
编译后的运行结果如下:
这就是DECORATOR模式的具体使用了,下次我们接着聊COMPOSITE模式。
参考书目:
1, 设计模式——可复用面向对象软件的基础(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英军等译 机械工业出版社
2, Head First Design Patterns(影印版)Freeman等著 东南大学出版社
3, 道法自然——面向对象实践指南 王咏武 王咏刚著 电子工业出版社
乱砍设计模式之一
最后更新于:2022-04-01 14:36:44
STRATEGY模式———赵子龙单骑救主
[junguo](#)
STRATEGY在中文中被译成了策略,我感觉这个意思并不妥切,但翻英文词典能得到的翻译也只有这个,我的词典比较简单,不知道是否还有其它意思?如果没有,那么我想可能和中国研制的CPU在研发阶段被定名为“狗剩”一样,它只是一个名字而已,并不能确切的代表真实的意义。经典著作《设计模式》中将策略模式定义为:定义一系列的算法,把它们一个个的封装起来,并且使它们可以相互转换。这个定义还是比较抽象,下面我将通过一个例子来具体的讲解策略模式。感觉这样更容易帮助新手理解模式,这也是《Head First Design Patterns》中讲解模式的方法。先来描述一下用到的例子的背景资料:
话说三国时期,刘备失去徐州之后,四处奔逃,寄人篱下。先投袁绍,后附荆州刘表,屯兵新野。好不容易有些转折,三顾茅庐请出了诸葛亮。但此后不久,曹操率领大军猝然而至,意欲扫平江南。刘备不及防范,更加之区区数千军马根本不是五十万大军的对手。无奈之下,率同新野军民逃奔襄阳。不意赶上刘表身亡,刘表之妻蔡夫人及蔡夫人之兄蔡瑁决议投降曹操,不给刘备开门。再度无奈,只好继续向南奔逃,但刘备带领数万百姓,行动迟缓,被曹军追上。刘备安排张飞断后,让赵云保护家小,自己继续向当阳逃窜。终还是被曹军包围了起来。赵云苦战之中走失了刘备家小,遂于乱军之中左奔右突寻找刘备妻小。不期碰到曹操随身背剑之将夏侯恩,子龙抢挑夏侯恩,夺了青釭宝剑(曹操有两把宝剑,另一把名为倚天剑)。子龙继续四处寻找幼主。终在一堵断墙边找到刘备之妻糜夫人和刘备幼子刘禅,糜夫人为了不拖累赵云护刘禅突围,跳井而死。赵云将刘禅护于胸中,纵马向外奔突。枪挑曹军数将,而后曹军一拥而上。短兵相接,子龙拔出了青釭宝剑,左挥右砍,勇不可挡,杀退曹军众将。由于曹操爱惜赵云人才,不许放冷箭,赵云幸而冲出了曹军包围圈。抱阿斗去见刘备。而后刘备摔了阿斗,有了我们都知道的谚语:刘备摔孩子——收买人心。
后人有诗赞曰:血染征袍套甲红,当阳谁敢与争锋!古来冲阵扶危主,只有常山赵子龙。声明一下,该诗完全是抄写罗贯中的。刚刚看到一件乐事:高晓松要起诉韩寒,原因是韩寒多年前写的小说《三重门》中引用了高的歌词,也真能想的出来。如今互联网上的趣事真多。
刘备五虎将中,赵云排名最后,确最受欢迎。雄姿英发,英勇强悍,确又心细如丝,体贴入微,实为千古男人之典范。以至千载之下犹有众多美女粉丝。如果设计一个以子龙为原型的游戏,定会吸引众多玩家,说不定还会拥有众多的女性玩家。玩过以前大型游戏机上的三国志,赵云挺帅,但太过简单,没有情节,感觉不过瘾。加上不是国产的,失去了不少感情。不知道现今的三国志一类的游戏是否有单骑救主这样的情节?由于本人游戏IQ太差,不是太关心这些。不管这些了,先来设计一下我们的这段情节,简单实现之(呵呵,说清楚了,该程序只是一个简单的文字输出,图形版的我还没这水平,这里只是教你如何使用策略模式)。
我们来看看要实现的功能,赵云手握长枪与曹军众将武斗,由于他夺来了青釭宝剑,所以他的兵器可以随时更换,但每次都只能使用一种兵器(不要和我抬杠,说他可以左手持剑,右手握枪;绞尽脑汁才想起这么一个例子,容易吗,我?)。而每种兵器的杀伤力并不相同。我们要实现的就是这么一个简单的功能。
首先我们帮赵云提炼出一个他所属的类——武将类,该类拥有武将名字,所使兵器等信息;还包括前进,冲锋等方法。先来看看类图:
![](image/d41d8cd98f00b204e9800998ecf8427e.jpg)
这个类拥有两个成员变量m_strName和m_strWeapon,分别表示武将名和武将使用的兵器。另有几个成员函数:Advance表示前进;Assault表示攻击,由于使用的武器不同,武器的使用及杀伤力并不相同,所以在我们现在设计的类中,Assault需要根据m_strWeapon的类型来实现不同的操作;SetWeapon用来设置武器的类型。
我们首先来想象一下Assault的实现,我们需要根据武器类型的不同来实现不同的操作,如果武器有数十种呢?那么最简单的方式就是,在Assault中加入switch … case,然后根据不同的case来实现功能。如果我们可以将各个case条件下的操作提炼成一个个函数,这样也许程序也不会太庞杂。不过我见过笨蛋写的程序,一个函数中有数十个case条件,每个case条件下都有数十上百行代码,整个函数搞到上千行;还好意思拿这样的函数向人炫耀,真是无知者无畏。再接着想,我们的兵器库不断的变更,每当增加新的兵器类型的时候,我们是不是都需要改Assault呢?那么原本已经测试好的东西,经过变动,又需要经历一次测试的洗礼,我们可以确保不给以前的程序带来问题吗?你改没改过这样的程序?我改过,整个过程就一个字:累。有没有方法帮我们避免这样的问题发生呢?有,当然有了!
解决这样的麻烦,我们应该牢记面向对象的一个原则:一个模块对扩展应该是开放的,而对修改应该是关闭的。那我们该如何做到在为我们的模块添加新型武器的时候,做到不需要修改原有的类呢?最简单的方法就是通过继承来实现,先看类图:
![](image/d41d8cd98f00b204e9800998ecf8427e.jpg)
将类General做了修改,并为它添加了两个子类GeneralWithLance(带长矛的将军)和GeneralWithSword(带剑的将军)。由于使用的兵器不同,Assault的实现不同,所以我们在子类中重载了Assault。这样当我们的程序中需要添加新的兵器类型的时候,我们只需要重新派生新的子类就可以解决问题了。这个世界是不是变得美好了一些,不需要去修改原有的代码,也就意味着我们可以少碰一些别人的代码。有过经验的人都知道,修改别人的代码,是件痛苦至极的事情。但不要高兴的太早,问题马上又来了。这时候有人提出应该考虑将军的坐骑,如水军统领的行动工具是船,而轻骑将军的行动工具应该是马,而且行动工具不同,将军的杀伤力也不同。我靠,整个世界又向黑暗倾斜了。想想我们当前的方法,再按继承的方式作,就需要再扩展类:骑马的带剑将军,乘船的持矛将军….而且每次添加一种兵器就需要相应得组合不同的行动方式(如下图所示)。可怕的现象出现了,随着兵器和行动方式的增多,类都可能成倍的增加。类越来越多,越来越难控制,这就是传说中的类爆炸了。这样的程序,你还如何去维护?不过到目前为止,我还没见过这样的程序。那些用C++写了十多年程序还只会select…case,而不知道用类的笨蛋,我不知道他们是只懂过程化设计?还是看到类膨胀而不敢使用类?不过类膨胀比结构化的程序更为可怕,面向对象也是把双刃剑,达到什么样的效果,就看应用人的水平了。
![](image/d41d8cd98f00b204e9800998ecf8427e.jpg)
我们想使用面向对象的特性,而且不想看到类膨胀,该如何办呢?那就应该记住另一条面向对象的原则:优先使用聚合,而不是继承。先来简单看看聚合的概念。
~~~
class IDCart {}; //身份证
class Person
{
public:
…..
private:
string name;
int age;
IDCart idcart;
};
~~~
(看到这个定义,基本可以确定该套系统是为中国公民做的。身份证对于身处外地打工的人来说是重要的。前段时间一哥们把身份证弄丢了,由于跳槽的缘故,他离开了原来所在的城市。人民警察要他把户口迁移出去,大城市的户口一般工作单位都不给办。迁哪儿去?想想诺大的中国那里是我们的容身之所?派出所百般刁难,不给补办。好不容易弄了一个临时身份证,拿着去银行注销银行卡,银行居然也不给办。哥们郁闷至极,比钱包丢的时候都郁闷。说是要户籍改革,不知道会改些什么?想想一年前,我被小偷顺走了钱包,那时候还是一代身份证,感觉办起来比现在方便了很多。户籍是要改革了吗?会改成什么样呢?)
看我们的定义,一个人拥有名字,年龄,身份证等属性。由于身份证有一些相关的操作:发放,挂失,补办等操作,我们把它提炼成一个单独的类。此处我们使用聚合的方式来完成对于身份证的处理,所有对于身份证的操作,都通过idcart来实现。如:发放身份证的操作,在聚合条件下就变成了:
~~~
class IDCart { Public: void PutOut(){} }; class Person { Public: Void PutOutIDCart() { idcart->PutOut(); } Void SetIDCart(IDCart cart) { idcart = cart; } Private: string name; int age; IDCart idcart; };
~~~
聚合说白了就是在一个类中定义一个另一个类的对象,然后通过该被聚合的对象来实现相应本需要聚合类实现的功能。
使用聚合的优点是:可以帮助我们更好的封装对象,使每个类集中在单一的功能上,使类的继承层次也不会无限制的增加,避免出现类数量的膨胀。而且使用聚合还有一个优点就是可以动态的改变对象(下面会讨论到)。不过聚合相对于继承来说,没有继承直观,理解比较困难。
在确定使用继承还是聚合的时候,有一个原则:继承体现的类之间“是一个”的关系。例如我们需要对学生,工人进行单独的处理。那么我们的例子应该是这样:
~~~
Class student : public person
{
};
Class worker : public person
{
};
~~~
也就是说学生是一个人,而工人也是一个人。学生和人之间体现的是“是一个”的关系。而工人也一样。
而身份证对于人来说,是人的一个属性。那么我们就可以提炼出来成为一个单一的类,通过聚合来实现。
接着还是回到我们策略模式的例子,同样在我们的例子程序中,可以把武器提炼成一个单独的类,类图如下:
![](image/d41d8cd98f00b204e9800998ecf8427e.jpg)
我们提炼出一个Weapon类,将在General中使用。噫!怎么又有一个m_strWeapon?你可能要开骂了:谁他妈是傻子呢?这样做不又回到了过程化设计的鬼样了?别急,提供这个错误的方法,只是为了给你提供另一个面向对象的设计原则:尽量针对接口编程,而不要针对实现编程。
C++中没有象C#或者Java等面向对象语言那样,提供对Interface的语言支持。但接口也不过是一个概念,我们使用纯虚函数类,等同于接口。我们提供一个不被实例化的基类事实上也可以当作接口来用。针对接口编程的意义是:可以不用知道对象的具体类型和实例,这样可以减少实现上的依赖性。可以帮助我们提高程序的灵活性。好了,我们再重新来设计类图:
![](image/d41d8cd98f00b204e9800998ecf8427e.jpg)
新的类图中Weapon被抽象成了一个接口,拥有一个虚函数Assault,拥有两个子类Lance和Sword。而在General类中,我们拥有了一个新的成员:m_pWeapon。而攻击的函数变成了performAssault,它是通过调用m_pWeapon->Assault()来实现攻击的。这样一来武器就可以随时变更了。我们来看看简单的代码实现:
~~~
//武器类
class Weapon
{
public:
virtual void Assault() = 0; //纯虚函数
};
//长枪类
class Lance : public Weapon
{
public:
virtual void Assault()
{
cout << " I kill enemy with the Lance and can kill 10 every time!" << endl;
}
};
//宝剑类
class Sword : public Weapon
{
public:
virtual void Assault()
{
cout << " I kill enemy with the sword and can kill 20 every time!" << endl;
}
};
//武将类
class General
{
private:
string m_strName;
Weapon *m_pWeapon;
public:
//构造函数,初始化m_pWeapon为Lance类型
General(string strName):m_strName(strName),m_pWeapon(new Lance())
{
}
//指针是需要删除的
~General()
{
if ( m_pWeapon != NULL ) delete m_pWeapon;
}
//设置武器类型
void SetWeapon(Weapon *pWeapon)
{
if ( m_pWeapon != NULL ) delete m_pWeapon;
m_pWeapon = pWeapon;
}
void performAssault()
{
m_pWeapon->Assault();
}
void Advance()
{
cout << "Go,Go,Go!!!" << endl;
}
};
int main(int argc, char* argv[])
{
//生成赵云对象
General zy("Zhao Yun");
//前进
zy.Advance();
//攻击
zy.performAssault();
//更换武器
zy.SetWeapon(new Sword());
zy.Advance();
zy.performAssault();
return 0;
}
~~~
其实程序的实现相当简单,就是一个简单的聚合加应用针对接口编程的例子。这就是我们要讲的第一个模式:策略模式。重新看一下它的定义:定义一系列的算法,把它们一个个的封装起来,并且使它们可以相互转换。这里所说的一系列的算法封装就是通过继承把各自的实现过程封装到子类中去(我们的例子中是指Lance和Sword的实现),而所说的相互转换就是我们通过设置基类指针而只向不同的子类(我们的例子上是通过SetWeapon来实现的)。
是不是很简单呢?如果你懂虚函数的话,千万别告诉我没看懂,这是对我无情的打击,也许导致我直接怀疑你的智商。如果你不懂虚函数,那回头找本C++的书看看吧,推荐的是《C++ primer》,第四版出了。
参考书目:
1, 设计模式——可复用面向对象软件的基础(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英军等译 机械工业出版社
2, Head First Design Patterns(影印版)Freeman等著 东南大学出版社
3, 道法自然——面向对象实践指南 王咏武 王咏刚著 电子工业出版社
4, 三国演义 网上找到的电子档
乱砍设计模式之二
最后更新于:2022-04-01 14:36:42
STATE模式———履胡之肠涉胡血,悬胡青天上,埋胡紫塞旁。
[junguo](#)
STATE模式的中文名称是状态模式。在《设计模式》一书中的定义是:允许一个对象在其内部状态改变的时候改变它的行为。对象看起来似乎修改了它的类(中文译书上的原话,不过我觉得这句话应该翻译成——对象显现出来的是改变了它所属的类)。看了定义还是感觉有些抽象,好的,我们还是通过一个例子来学习该模式。还是先从情节设计开始:
严风吹霜海草凋,筋干精坚胡马骄。
汉家战士三十万,将军兼领霍嫖姚。
流星白羽腰间插,剑花秋莲光出匣。
天兵照雪下玉关,虏箭如沙射金甲。
云龙风虎尽交回,太白入月敌可摧。
敌可摧,旄头灭,履胡之肠涉胡血。
悬胡青天上,埋胡紫塞旁。
胡无人,汉道昌。
李白这首诗豪气万丈的诗篇,描述的是汉骠骑将军霍去病率领大军,出陇西,突袭匈奴部落的战争场面,千载之下,犹让人感觉豪迈异常。匈奴和汉朝的争端从汉高祖刘邦开始。楚汉之争过后,刘邦统一了中国。此时,匈奴部族也被冒顿(这两字发音应为mo du)单于统一了起来。匈奴逐渐强大的同时,开始窥觑汉朝的疆土。冒顿单于领军进犯太原,包围晋阳。刘邦亲率30万大军进击匈奴,不期竟中匈奴之计,被困于白登。幸亏用陈平之计,送重礼于冒顿夫人,走枕边风路线,才让匈奴大军让开一条路,逃了出来,狼狈之极。这场战役的结果就是汉朝采取和亲政策,送汉宗室之女给匈奴王为妾,并借机进贡。但这也避免不了匈奴侵犯汉族边界,杀戮汉民,抢夺牲畜。这样的历史持续了数十年,直到汉武帝的时候还保持着。而雄才大略的武帝,岂能容忍此奇耻大辱。坚毅勇猛的他,筹划着对匈奴的打击,经过一系列的改革(个人觉得他改革兵种,将以车兵步兵为主的军队改为轻骑兵为主,对这场战役意义最为重大),开始了对匈奴的穷追猛打。这段历史造就了中国历史上两颗耀眼的将星:卫青,霍去病。霍去病更加夺目,他18岁的时候,随卫青大军出征。他率领800轻骑,远离大军,奔赴敌人腹地,斩敌二千余人。被汉武帝封为冠军侯。后又率1万大军出陇西,转战千余里,斩获8000于人。再于同年秋天,出北地,攻祁连山,大获全胜,斩获三万余人。汉朝与匈奴的最后一次大型战役也由卫青,霍去病完成。霍去病亲率5万骑兵远离代郡,对匈奴人穷追猛打,歼敌七万余人。乘胜追击到狼居胥山(今在蒙古境内),并在此举行祭天仪式(封狼居胥)。此时的霍去病仅仅21岁,但二年后英年早逝。在他短暂的一生中四次与匈奴做战,转战数千余里,灭敌十余万人,彻底熄灭了匈奴人的嚣张气焰,也给大汉的边境带来了安宁。不仅如此,他还留下了“匈奴未灭,何以家为”的千古豪言。真是铮铮男儿形象,为万世铁骨男儿楷模。
前年看到的一部连续剧《汉武大帝》,总体感觉还行。不过太过小气,为了一些戏剧效果,把很多历史事件强塞了进去。如曹操为人捉刀的故事,被安排到了汉武帝身上;为了那么一段冒顿单于鸣镝杀父的情节,让他晚生了数十年,居然他还成了汉武帝的死敌(为汉武帝安排这么一个对手,我总觉得太小觑了汉武帝)。
还有些事,本不想写了,后来想想还是写吧。反正咱这是乱砍,就随性而来吧。网上有个臭名昭著的网站,叫做汉网。集聚着一群民族主义分子,提倡汉服(不过他们好像也不是提倡每天都穿),说这样可以帮助中国人恢复民族自尊心。让我想起阿Q说过的:老子原前也阔过。还有明显意淫古人的意味,翻看历史,我们可以看到武帝之前,汉族一直受匈奴人的欺凌。而武帝之后,国力衰落,虽然周边的小国还算顺服,但汉朝还是失去了武帝时的雄霸之气。试想想如果当时情况下的皇帝不是汉武,而是一个软弱无能,如明朝的那个建文帝,估摸着汉朝内部都搞不定。更甭谈打击匈奴了,而消灭匈奴取得成功,也和武帝破格提拔的将领有关。试想没有卫青,霍去病又会是什么情况?第一次攻击匈奴,四路人马出击,两路被败(著名的飞将军李广被俘,而后逃脱),一路无功,只有卫青有所斩获。而后的多次出击中,除卫青,霍去病外的其他将领多有败绩。在电视《汉武大帝》中,李广的死被安排成了为了成全卫青的计策,吸引匈奴,他主动陷于匈奴包围圈中,英勇战死(这段情节,描写的匈奴人也太弱智了)。我一哥们看到这情节后,哈哈大笑。然后他津津有味的为别人讲解李广真正的死因:李广是路痴死的。李广出击匈奴的战争中多次迷路,最后一次因为卫青派人问他迷路的原因,李广自杀身亡。李广是汉朝名将,但我觉得他不适合那个年代。从战国时代起,名将都以智略取胜。因为战争基本都以攻城拔地的形式进行,都有相对固定的战争场地。武器也以战车,重骑兵为主,行动较为缓慢,主要打的是阵地战。而当时匈奴,采取的是打得过就打,打不过就跑得游击套路(呵呵,应该是我军游击战的雏形了)。面对这样的对手,更需要的就是霍去病这种有锐气,深沉勇猛,敢于追着敌人背后猛打的将领(我觉得国民党将领薛岳也属于此类战将)。当汉武让霍去病学习古兵法的时候,他的回答是:顾方略何如耳,不至学古兵法。也许正是这种藐视权威的气概成就了霍去病,也成就了“犯大汉者,虽远必诛”的大汉豪情。但这段历史的演绎里离不开汉武帝,也离不开霍去病(战功最著的是他,单靠卫青可能这场战争很难如此彻底),是他们的组合成就了这段历史。没有这样的组合,也许大汉天下会被匈奴的铁蹄践踏,而汉文明也许会象希腊文明那样被游牧民族践踏而消亡。历史充满了偶然性,历史本身并不值得我们拿来炫耀。我觉得如今的中国人没有什么不自信的,我也不觉得现在的中国人没古代好,伟大的时代总离不开伟大的人物。也许我们的民族只是现在缺少一个这样的人物,那也就不好怨天尤人了,因为身处这个时代的我们,没有一个是伟人,我不是,你也不是,那你怪谁去?好了,不扯了,还是认真做我们的Coding生涯吧。
我们以骠姚将军深入漠北追歼匈奴人的情节作为我们的例子,这次我们将五万骑兵作为我们的对象。这五万骑兵作为一个作战整体,会有一些这样的一些状态:休整,前进,攻击。整个作战过程中都在这些状态下进行着。我们先来看看下图:
![](image/d41d8cd98f00b204e9800998ecf8427e.jpg)
这里我们看到一个转换图:共有三种状态Advaneing(前进),Assaulting(攻击状态),Resing(休息状态)。三个状态之间可以进行转换,Advaneing和Resing可以直接转换,Advanceing和Resing可以直接转换到Assaulting状态,而Assaulting只能在敌军被全歼的时候才能转化到Resing状态。
如何来实现该系统呢?我们还是首先用土鳖似的方式来实现第一个系统,这样更有助于看到模式的优点。我们首先建立一个Army类,如下:
~~~
#define ADVANEING 0
#define ASSAULTING 1
#define RESTING 2
Class Army
{
Private:
int m_iState;
int m_iEmptyCout;
public:
Army():m_iState(RESTING){}
Void Advance();
Void Assault();
Void Rest();
};
Void Army::Advance()
{
If (m_iState == ADVANEING )
{
Cout << “Are Advaning!” << endl;
}
Eles if ( m_iState == ASSAULTING )
{
Cout << “sorry! Are assauling!Can’t Advace” << endl;
}
Else if( m_iState == RESTING )
{
m_iState = ADVANING
Cout << “ok!Go!” << endl;
}
}
Void Army:: Assault ()
{
If (m_iState == ADVANEING )
{
m_iEmptyCout = 100;
m_iState == ASSAULTING;
Cout << “ok!Assault!” << endl;
}
Eles if ( m_iState == ASSAULTING )
{
m_iEmptyCount -= 100;
Cout << “Are assauling!” << endl;
}
Else if( m_iState == RESTING )
{
m_iEmptyCout = 100;
m_iState = ASSAULTING;
Cout << “ok! Assault!” << endl;
}
}
Void Army:: Rest ()
{
If (m_iState == ADVANEING )
{
m_iState == RESTING;
Cout << “ok!Rest!” << endl;
}
Eles if ( m_iState == ASSAULTING )
{
Cout << “Are assauling!can’t Rest” << endl;
}
Else if( m_iState == RESTING )
{
Cout << “Are Resing!” << endl;
}
}
~~~
好了这样我们的类就完成了,虽然看起来有些杂乱,但运行应该没有什么问题。这样完成虽说土了一些,但事实上并不影响它的正常运行。但我们需要考虑的一个问题是:当需求变化的时候,我们的程序该如何去改?软件界的一个规律就是需求一直在变更,变更伴随着软件的生存到死亡的过程。如今流行的设计模式,重构,测试驱动开发等技术目的都是为了适应需求的变更,而将程序修改的难度降到最低来。所以我们来考虑这样的情况,由于骠骑将军取得了大胜,举行了祭天仪式,祭天仪式中战士兴奋度提高,杀敌热情暴增。所以骠骑决定将这个仪式加入到战斗安排中,当取得胜利的时候,举行祭天仪式。而这又是一个新的状态,该状态只有在Rest状态下才能切换过去,我们该如何去修改程序呢?以目前的做法,我们需要在每个函数中添加条件,修改函数,这样又与我们在策略模式中提到的规则“一个模块对扩展应该是开放的,而对修改应该是关闭的”背道而驰了。怎么解决呢?还是同样的方法:提炼出一个类。同样为了解决动态改变状态的需求,我们还应该记着另一个规则:尽量针对接口编程,而不要针对实现编程。闲言少叙,我们还是看类图,这样来的快一些。
![](image/d41d8cd98f00b204e9800998ecf8427e.jpg)
从类图,我们可以看到Army类中拥有一个Station类的对象,它所有的操作将通过该对象来实现。是不是发觉和策略模式很相似?先不说这个,我们先看完例子再说。看看具体代码:
我们首先看以下State接口,很简单,就是几个纯虚函数。
~~~
class State
{
public:
virtual void Advance() = 0;
virtual void Assault() = 0;
virtual void Rest() = 0;
virtual void Fiesta() = 0;
};
~~~
我们再来看一下AdvanceState,我们看到在AdvanceState中有一个Army对象的指针,是因为需要在内部修改状态,具体的代码中可以看到。
~~~
class AdvanceState : public State
{
private:
Army *m_pArmy;
public:
AdvanceState(Army *pArmy);
virtual void Advance();
virtual void Assault();
virtual void Rest() ;
virtual void Fiesta();
};
我们再来看一下AdvanceState的具体实现:
AdvanceState::AdvanceState(Army *pArmy):m_pArmy(pArmy){}
void AdvanceState::Advance()
{
cout << "Be in Advancing!" << endl;
}
void AdvanceState::Assault()
{
//设置假想的敌人数
m_pArmy->SetEmptyCount(200);
cout << "Ok!Assault!" << endl;
m_pArmy->SetState(m_pArmy->GetAssaultState());
}
void AdvanceState::Rest()
{
cout << "OK!Rest!" << endl;
m_pArmy->SetState(m_pArmy->GetRestState());
}
void AdvanceState::Fiesta()
{
cout << "sorry!can't Fiesta!" << endl;
}
~~~
很简单了,就是根据当前状态来处理各个函数。我们看到有这样的函数m_pArmy->SetState(m_pArmy->GetRestState());是用来修改Army所处的状态的。在Army类中,我们可以看到它的具体实现。其它几个状态类的实现类同,就不房到这里了,感兴趣的可以到附件中自己找。
我们再来看看Army类的定义:
~~~
class State;
class Army
{
private:
State* m_pState;
//保存各个状态指针便于使用,当有新的状态填加的时候,我们也需要在此处添加
State* m_pAdvanceState;
State* m_pAssaultState;
State* m_pRestState;
State* m_pFiestaState;
int m_iEmptyCount;
public:
Army();
void SetState(State *pState);
State* GetAdvanceState();
State* GetAssaultState();
State* GetRestState();
State* GetFiestaState();
void Advance();
void Assault();
void Rest();
void Fiesta();
void SetEmptyCount(int iEmptyCount){m_iEmptyCount = iEmptyCount;}
int GetEmptyCount(){return m_iEmptyCount;}
};
~~~
它的实现:
~~~
Army::Army()
{
m_pAdvanceState = new AdvanceState(this);
m_pAssaultState = new AssaultState(this);
m_pRestState = new RestState(this);
m_pFiestaState = new FiestaState(this);
m_pState = m_pRestState;
m_iEmptyCount = 0;
}
void Army::SetState(State *pState)
{
m_pState = pState;
}
State* Army::GetAdvanceState() {return m_pAdvanceState;}
State* Army::GetAssaultState() {return m_pAssaultState;}
State* Army::GetRestState() {return m_pRestState;}
State* Army::GetFiestaState() {return m_pFiestaState;}
void Army::Advance() {m_pState->Advance();}
void Army::Assault() {m_pState->Assault();}
void Army::Rest() {m_pState->Rest();}
void Army::Fiesta() {m_pState->Fiesta();}
~~~
其实也没什么了。很容易的理解的。不知道汉武时代有没有过阅兵仪式,如果有,那就会又多一个状态,想想我们该如何解决?挺简单了,为State添加一个新的子类,并为它提供一个阅兵的启动方法,当然相应的子类也需要添加。相应的Army类中也需要添加该方法。这样做,我们只是扩展了原有类的方法,而不会去改动它原有的功能。这样就可以避免给原有功能带来bug了。
再看看该类的调用:
~~~
int main(int argc, char* argv[])
{
Army army;
army.Advance();
army.Assault();
army.Rest();
army.Fiesta();
army.Assault();
army.Assault();
army.Rest();
army.Fiesta();
system("pause");
return 0;
}
~~~
创建对象后,我们可以直接调用它的函数来实现状态的转换了。
好了,状态模式,我们先讲到这里了。回想一下上回的策略模式,是不是觉得很象?在《Head First Design Model》中,该模式开篇的扉页上画的是一幅煽情的母亲流泪的图片,说她眼瞅着自己的孩子分离,此处的两个孩子就是策略模式和状态模式。我们可以这两个模式的原理都是将一个类中的属性提炼成一个接口,再通过该接口实现对其子类的调用而完成所属类的功能。它们的不同是状态模式的接口的变化是在接口子类中完成的,而策略模式是通过所属类来完成的。差别只是这一点,具体工作中使用哪个模式,那就的具体问题具体分析了。你只需记住我们红体标记的规则,就可以以不变应万变了。
参考书目:
1, 设计模式——可复用面向对象软件的基础(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英军等译 机械工业出版社
2, Head First Design Patterns(影印版)Freeman等著 东南大学出版社
3, 道法自然——面向对象实践指南 王咏武 王咏刚著 电子工业出版社
4, 史记 网上找到的电子档
乱砍设计模式之零——序
最后更新于:2022-04-01 14:36:39
乱砍设计模式之零——序
作者[junguo](#)
有时候想知道偶然会为人生带来什么样的意义?作为一个怀疑论者,我对人生充满了疑虑,对于偶然所起的作用也不是那么确定。但还是可以总结一些自己并不确定的结论。大学期间,成天旷课的我,那天偶然上了一堂软件工程课(我不是计算机专业的,我们开这堂课本身就有些古怪)。那天老师不知道是一时兴起,还是早有准备,在下课前他在黑板上画了一个图,标注了学习计算机的进阶图。这堂偶然的课,给我带来了后来的失落和彷徨。
一个编程工具(VC,Delphi)
一门编程语言(C,Pascal)
常用软件的熟练使用
计算机硬件(说白了就是装机的水平)
他提供的进阶图如上所示,他似乎没有说C和Pascal应该学到什么样子。而当时的我每天泡在图书馆或者宿舍里,看的是数据结构,编译原理一类的书。我为自己绘制的进阶曲线是学习计算机系得所有基础课程,然后考高级程序员。那时候对VC等工具并不感兴趣,我觉得还是打好基础容易进阶。但听完这堂课后,我的思路被打断了,放弃了自己原有的理念,跑到书店买了一本VC基础的书,那是我学生时代买的最贵的书(70多或者80多块,书借人了),书上都是一个一个的例子,其实学完后我都没弄明白MFC是怎么回事。只是学会了拖动不同的控件,在界面上做出不同的效果。凭良心说,那本书译文的文笔不错,也挺适合入门的,但它确实是本不折不扣的破书(国外图书也不都是精品,垃圾也不少),在不懂C++的情况下,它可以教会你在界面上拖拉的本事,屏蔽了你学习的路线。而我们老师的进阶图无疑也是一张误导图,编程还是以数据结构等内容为基础的,没有了基础,你也就没有了前进的依仗。经过多年的失落彷徨,终于感觉找到了入门的通道。我也想总结一幅进阶图,但我发现自己总结不出来。庞庞杂杂接触了太多的东西:汇编,破解,操作系统,C++,ASP,MFC,COM,ATL,VB,STL,数据库;但接触的东西都没有到精通的地步,只是感觉现在理解东西没有以前那么复杂了,但为什么会这样,我自己也说不清楚。只是隐隐约约感觉理解这些东西和汇编有些关系,但这些都是在我单纯学习汇编时候并没有感觉到的。我如今也只是处于从初级向中级攀升的阶段,真要搞出一个进阶路线,可能效果就和我们老师的进阶图一样只能误人子弟了。所以如今放弃了这方面的努力,也许将来有一天我会去做。如今我想做的就是提供给那些和我一样在从初级向中级攀升的人们一些共同感兴趣的话题。而设计模式绝对是这样一个话题,设计模式是帮助你真正理解OO设计的一把钥匙,也许只有它能帮助你真正进入OO设计之门。在没接触设计模式之前,对于OO只知其形,不知其意。很多OO设计的原则,不知道我买的那本巨著《C++ Primer》中有没有,反正我是一条也没记住。如果你觉得你懂了面向对象的基本原理,懂得了虚函数,需要继续紧阶,那么我们可以一起探讨这里谈到的设计模式。
GOF出的《设计模式》买了很长时间了,翻看了也不下三篇,但每次总感觉收获不大。虽被众人奉为经典,但那本书不适合我,写的太过抽象,没有完整的例子,每次看过都没留下多少印象。又是一个偶然,给我带来了惊喜。我在网上找到了一份电子版的《Head First Design Patterns》,尽管只有第三章,但我发现这本书文笔清新,事例翔实,以前看多次没记住的东西,这儿看一次就留下了深刻的印象,实是一本入门的好书。所以豪不犹豫买了本纸版的,不过如今该书只有影印版,但并不影响阅读。以我大学英语四级没过的水平都可以基本看懂这本书,我想搞计算机的应该都不成问题。
刚看到这本书的时候,第一个想法是自己能不能把它翻译一下。与大家共享,但后来想想版权什么的东西,自己并不清楚。加之文笔拙劣,怕毁了原文的意境,所以放弃了这个念头(毕竟我不是人民教师)。但总感觉有些失落,那就自己写吧,把我能理解的总结出来,配以我想到例子。经过这么一番折腾,也帮助自己加深理解,因为看书的时候,还是会忽略太多的细节,只有经过了自己的手,才会抠出很多容易忽略的东西。这就是我写这个系列的原因了。但也希望有同样兴趣的同仁共同讨论了。
冠以乱砍的名字,是因为我不想把技术的东西搞得太枯燥。尽量加一些自己感兴趣的内容进去,呵呵,有同样爱好的同仁,我们也可以一起讨论。但对于技术的内容,我还是尽最大的努力做到正确表述。
好了,要说的说完了。但声明一下,今天看到自己以前写的一篇文章被转载,但被斩头去尾,还删除了署名。虽然不是太在意,但还是感觉不舒服。所以希望有仁兄要转载的话,请保留署名;不要删除任何内容,如果你不喜欢我的废话,那就不要转了。
前言
最后更新于:2022-04-01 14:36:37
> 原文出处:[乱砍设计模式](http://blog.csdn.net/column/details/design2.html)
作者:[junguo](http://blog.csdn.net/junguo)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
# 乱砍设计模式
> 冠以乱砍的名字,是因为我不想把技术的东西搞得太枯燥。尽量加一些自己感兴趣的内容进去,呵呵,有同样爱好的同仁,我们也可以一起讨论。但对于技术的内容,我还是尽最大的努力做到正确表述。