话说最近项目重构,整理了一些以前的人写的代码,看到有很多功能重复的地方。其中有一部分是很多重复2小时检查一次操作的代码,比如2小时客户端检查一次更新,2小时从服务器上请求一次数据看是否需要弹框,2小时检查一次是否有下发指令指令,2小时检查一次有没过天要重复递交运行数据,2小时检查一次是否过天需要重新刷新用户登录的积分等等,除此之外也有很多半小时一小时检查请求一次的操作,而最初实现第一个功能的时候也没考虑到后续会增加这么多以至于现在好多检查或者请求都是独自为战因而扩展性极差。这种扎堆的操作很容易想到必须为这里操作设计一个新的方案。但是这个方案除了尽可能的减少2个小时一次的检查次数(重构的三次法则),其次具备良好的扩展性,倘若再次增加2个小时检查一次的选项必须不影响之前的逻辑(设计模式的原则之一:开闭原则)。
如果是当初刚学编程恐怕我会这么去写:
while (TRUE) { GetPopInfoFromSrv(); GetUpdateInfoFromSrv(); SendRunInfoToSrv(); Sleep(2*60*60*1000); }
其实这么写也没什么问题,也能满足上面说的的条件,需要再加什么功能只需要在Sleep之前加入一行写好的函数调用就行了。但是作为C++程序员是不应该写出这种代码的。仔细看来这种写法耦合度还是很高,如果要最大化减少代码的耦合性,我们要尽可能的做到这里的2小时检查一次的操作和我们真正要调用的代码完全解耦开来。也就是说如果我们再增加2小时一次的操作要尽可能不该动原来的代码而只是在原来的代码基础上进行扩展,这也是设计模式的开闭原则。也许有人会说这样做不是自己和自己较劲吗?个人觉得作为一个好的程序员应该养成没写一行代码都要有着怎么写能写更好的意识,这样才能迫使自己进步。 其实可以想象,把每隔2个小时产生的事件想象成一部电影,而需要做的事比如检查更新,递交数据想象成观众。一旦事件产生或者说电影开始那么所有观众都能看到,于是引出了设计模式中一个比较经典的模式---观察者模式。
观察者模式主要思想就是把观察者与被观察的对象分开实现解除两者之间的耦合,同时观察者实现抽象化,被观察者通过抽象接口与观察者联系起来。依照上面的例子实现观察者类如下:
1 class CObserverBase 2 { 3 public: 4 CObserverBase(){} 5 virtual ~CObserverBase(){} 6 virtual void ExecmdCode() = 0; 7 }; 8 9 class CSendRunInfoObserver : public CObserverBase 10 { 11 public: 12 virtual void ExecmdCode() {printf("CSendRunInfoObserver ");} 13 }; 14 15 class CGetUpateInfoObserver : public CObserverBase 16 { 17 public: 18 virtual void ExecmdCode() {printf("CGetUpateInfoObserver ");} 19 }; 20 21 class CGetPopInfoObserver : public CObserverBase 22 { 23 public: 24 virtual void ExecmdCode() {printf("CGetPopInfoObserver ");} 25 };
而被观察则需要一个专门设计的管理类,两者通过抽象接口结合起来。这个管理类可以按照下面的方法设计:
1 class CAutolock 2 { 3 public: 4 CAutolock(PCRITICAL_SECTION pCriticallock) 5 { 6 assert(pCriticallock); 7 m_pCriticallock = pCriticallock; 8 EnterCriticalSection(m_pCriticallock); 9 } 10 ~CAutolock(void) 11 { 12 LeaveCriticalSection(m_pCriticallock); 13 } 14 private: 15 PCRITICAL_SECTION m_pCriticallock; 16 }; 17 18 #define AUTO_LOCK(xCSName) CAutolock xCSName##lock(&xCSName); 19 20 class CConcreteSubject 21 { 22 public: 23 CConcreteSubject() {InitializeCriticalSection(&m_Criticallock);} 24 ~CConcreteSubject(){ DeleteCriticalSection(&m_Criticallock); } 25 void ExecmdCode() 26 { 27 AUTO_LOCK(m_Criticallock); 28 list<CObserverBase*>::iterator it = m_pObServerBaselist.begin(); 29 while (it != m_pObServerBaselist.end()) 30 { 31 if (*it) 32 { 33 (*it)->ExecmdCode(); 34 } 35 36 ++it; 37 } 38 } 39 40 void AddObserverBase(CObserverBase *pObserverBase) 41 { 42 AUTO_LOCK(m_Criticallock); 43 m_pObServerBaselist.push_back(pObserverBase); 44 } 45 46 private: 47 CRITICAL_SECTION m_Criticallock; 48 list<CObserverBase*> m_pObServerBaselist; 49 };
其中CAutolock类是一个自动释放的锁,本例中通过AUTO_LOCK宏来操作,本质就是在CAutolock对象构造的时候进锁,析构的时候释放锁。这么一来即使锁中间抛出异常也可以保证对象析构时释放锁。有点类似STL的智能指针auto_ptr.回归主题,整个过程调用就是在ExecmdCode函数内部。
调用过程便如下所示():
1 int main() 2 { 3 CConcreteSubject ConvertSubject; 4 CObserverBase* pSendRunInfoObserver = new CSendRunInfoObserver; 5 ConvertSubject.AddObserverBase(pSendRunInfoObserver); 6 7 CObserverBase* pGetUpateInfoObserver = new CGetUpateInfoObserver; 8 ConvertSubject.AddObserverBase(pGetUpateInfoObserver); 9 10 CObserverBase* pGetPopInfoObserver = new CGetPopInfoObserver; 11 ConvertSubject.AddObserverBase(pGetPopInfoObserver); 12 13 while (TRUE) 14 { 15 ConvertSubject.ExecmdCode(); 16 Sleep(2*60*60*1000); 17 } 18 19 delete pSendRunInfoObserver; 20 delete pGetUpateInfoObserver; 21 delete pGetPopInfoObserver; 22 getchar(); 23 return 0; 24 }