« 联结FBO与Texture Array3D数学:求点到直线的投影 »

伺机而动,设计模式之观察者(Observer)

今天看了BOB大叔的《敏捷软件开发》一书中,关于观察者模式的实现讨论,颇有感慨,于是有此文。——ZwqXin.com

最早接触设计模式是通过国内那本《大话设计模式》吧,通俗易懂地介绍了GOF的23个经典设计模式,可是我看后就忘了TAT,归根还是实践太少。BOB大叔此书讲述的模式不全,而且杂七杂八的也有些在世界领域少言及(乃至我甚至有时认为是不是大叔原创的- -),但是模式的讲述间传递着这么个思想:咱们模式不是本书主角。的确,模式通常只为了传递软件设计思想,而其本身不想成为思想——每本关于设计模式的书籍的作者大概也时不时在书中提及一下这种理念,但他们明显只是为了洗清自己对读者错误引导的嫌疑而已。《敏捷软件开发》一书中的模式只是为了给之后的案例做准备,它不关于设计模式因此无义务给你详解一个一个的模式。但相信读完此书后,你能谅解模式,你不再把它当作明星,而可能是当作朋友。

本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
      原文地址:http://www.zwqxin.cn/archives/cpp/learn-design-pattern-obsever.html

设计模式之观察者(Observer)一章是我到现在为止所见的最真诚的设计模式介绍。从一个设计任务开始,思考,重构,最后成其模式。就结果来说我相信是观察者模式的样式中最简单的,简单到可以让人直观其本质,当然这多得BOB大叔的耐心讲解。好了,来看看我看书后顿生的歪主意:

一场射击比赛中,观众霸好头位,准备为他们相中的设射击手呐喊助威。于是,我想到了一个射击手类(shooter)和一个观众类(Audience)。故事围绕一个射击手和三名观众。我假设射击手射中与否是随机事件,用随机函数rand()来决定两种状态。shoot行为引发状态的改变附加一句射击手的胡言乱语,观众看到射击结果后作出反应:

 
  1. #include "stdafx.h"
  2. #include <cstdlib>
  3. #include "windows.h"
  4.  
  5. class Shooter
  6. {
  7. private:
  8.     enum{BINGO, TAT};
  9.     int state;
  10. public:
  11.     Shooter(){state = TAT; };
  12.     void Shoot(int random)
  13.     {
  14.         if(random%2 == 0){state = BINGO; printf("射手:好野!打中了!\n"); }
  15.         else {state = TAT; printf("射手:5555!打不中!\n"); }
  16.     }
  17.     int GetState(){return state;}
  18. };
  19.  
  20. class Audience
  21. {
  22. private:
  23.     enum{WOW, DAMN};
  24.     int state;
  25. public:
  26.                    Audience(int _state){ state = _state; }
  27.     void Reaction()
  28.     {
  29.         if(state == DAMN){ printf("观众:咦~~有无搞错啊,甘都打5中~~~食sh*t啦你~\n\n"); }
  30.         else if(state == WOW){printf("观众:哇,打中了!好犀利啊~~~奖旧sh*t比你食喇~\n\n"); }
  31.     }
  32.  
  33. };
  34.  
  35. int main(int argc, char* argv[])
  36. {
  37.     srand(GetTickCount());
  38.  
  39.     Shooter shooter;
  40.     shooter.Shoot(rand());
  41.  
  42.     Audience audience(shooter.GetState());
  43.     audience.Reaction();
  44.  
  45.    shooter.Shoot(rand());
  46.     Audience audience2(shooter.GetState());
  47.     audience2.Reaction();
  48.  
  49.    shooter.Shoot(rand());
  50.     Audience audience3(shooter.GetState());
  51.     audience3.Reaction();
  52.     
  53.     return 0;
  54. }

注意,在main引导的三次射击中,观众1,2,3分别发表了“评论”,他们必须先从这个shooter的GetState()方法中得知射击结果。

然而,为了不影响射击手的情绪,比赛决定在一个对外封闭的场馆里进行(那观众还来干啥!?),观众怎么知道赛况?怎么联系两者?我忽略公告板的存在,于是我采用BOB大叔给的建议:由射击手发出结果信息,观众将实时接收信息作出反应。先看看main函数的改变:

 
  1. int main(int argc, char* argv[])
  2. {
  3.     srand(GetTickCount());
  4.  
  5.     Shooter shooter;//有一个射击手
  6.     Audience audience1(1,&shooter);//3个观众想关注这个射击手
  7.     Audience audience2(2,&shooter);
  8.     Audience audience3(3,&shooter);
  9.  
  10. //设定:射击手要把结果通知这3个观众
  11.     shooter.registerObserver(audience1);     
  12.     shooter.registerObserver(audience2);
  13.     shooter.registerObserver(audience3);
  14.  
  15.     shooter.Shoot(rand());//射手照常射击,观众会自动获知结果
  16.     shooter.Shoot(rand());
  17.     shooter.Shoot(rand());
  18.     
  19.     return 0;
  20. }

你可以发现最大的不同在于关键字“自动”。registerObserver是为了映射这种自动通知关系的。在这里,射击手扮演了这个故事的主角——行动表演者(performer),而三个观众扮演了旁观者,也就是“观察者”(observer)。因为我用的是较简单的“拉”模型,因此观众同样是需要获得射手的射击情况(状态)的(与此相对,“推”模型里射击手主动把结果告知观察者),但这次不同的是行动被内化了——就测试代码表层来说,只要射击手“同意”把该观众作为自己的“通知”对象,该观众就有资格获得射击手射击成败的信息。接下来射击手只管射击和自我咏叹(Shoot方法),而观众“自动”作出反应。

首先,抽象出双方的接口,然后,Subject类维护了一个名单,也就是针对某射击手的观察者名单,具有注册和通知功能(这是最基本的,可自加其他功能,譬如移除等),registerObserver把一个新的观察者引用加入通知列表,notifyObserver() 引起各个注册者的行动。

 
  1. class Performer
  2. {
  3. public:
  4.     virtual ~Performer(){};
  5.     virtual int GetState() = 0;
  6. };
  7.  
  8. class Observer
  9. {
  10. public:
  11.     virtual ~Observer(){};
  12.     virtual void Reaction() = 0;    
  13. };
  14.  
  15. class Subject
  16. {
  17. private:
  18.     std::vector<Observer*> ObserverVessel;
  19. public:
  20.     virtual void notifyObserver()
  21.     {
  22.         for(std::vector<Observer*>::iterator p = ObserverVessel.begin(); p!= ObserverVessel.end(); p++)
  23.             (*p)->Reaction();
  24.     }
  25.  
  26.     virtual void registerObserver(Observer &newobsever)
  27.     {
  28.       ObserverVessel.push_back(&newobsever);
  29.     }
  30.  
  31. };

显然,名单归射击手所有,因此接下来Shooter要扮演这个Subject的角色,同时也作为行动者。因为BOB大叔的那代码是JAVA写的,我不知道C++怎么处理JAVA和C#里那种“既继承某接口又implement某类”的表达方式,能想到的只有多继承了,抱歉。注意,简单起见,我仅规定Audience类接受单一关注者(myperformer),并把该被关注者(也就是射手)的射击状态关联观众的状态(“拉”模型):

 
  1. class Shooter : public Performer, public Subject
  2. {
  3. private:
  4.     enum{BINGO, TAT};
  5.     int state;
  6. public:
  7.     Shooter(){state = TAT; };
  8.  
  9.     void Shoot(int random)
  10.     {
  11.         if(random%2 == 0){state = BINGO; printf("\n射手:好野!打中了!\n"); }
  12.         else if(random%2 == 1){state = TAT; printf("\n射手:5555!打不中!\n"); }
  13.         notifyObserver();
  14.     }
  15.     virtual int GetState(){return state;}
  16. };
  17.  
  18. class Audience : public Observer
  19. {
  20. private:
  21.     enum{WOW, DAMN};
  22.     Performer *myperformer;
  23.     int audince_ID;
  24.     int state;
  25. public:
  26.     Audience(int audince_id, Performer *_performer)
  27.         :audince_ID(audince_id), myperformer(_performer)
  28.     {}
  29.  
  30.     virtual void Reaction()
  31.     {
  32.         state = myperformer->GetState();
  33.         if(state == DAMN){ printf("观众%d:咦~~有无搞错啊,甘都打5中~~~食sh*t啦你~\n", audince_ID); }
  34.         else if(state == WOW){printf("观众%d:哇,打中了!好犀利啊~~~奖旧sh*t比你食喇~\n", audince_ID); }
  35.     }
  36. };

当然,以上只是设计模式之观察者(Observer)的最简单应用,但希望能让看官稍微感受它的思想。恩,它不是公式,而是软件设计思想——这种思想我谓之“伺机而动”。

以上歪主意的代码:ObserverTest.rar

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

IE下本页面显示有问题?

→点击地址栏右侧【兼容视图】←

日历

Search

网站分类

最新评论及回复

最近发表

Powered By Z-Blog 1.8 Walle Build 100427

Copyright 2008-2024 ZwqXin. All Rights Reserved. Theme edited from ipati.