观察者模式

来源:互联网 发布:ubuntu qq下载 编辑:程序博客网 时间:2024/05/16 00:54

观察者模式

  意图: 
  定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
  结构:

  优点:
目标(Subject)与观察者(Observer)间抽象耦合
支持广播通信/通知
  缺点:
会带来意外更新问题
  示例:
  考虑这样一个例子:想知道公司最新的MM情报吗?加入我们MM情报邮件组吧。您只需要向我们发送一封订阅邮件即可,我们会把最新的MM情报以电子邮件形式通知您。
  现在我们来一步一步实现。很明显,示例中关心MM情况的人物即为订阅者,我们以Subscriber表示这一类人。在定义Subscriber之前我们先定义一个MMStatus枚举,用以表示MM状态:
enum MMStatus {Dining, Sleeping, Working};
这里定义了三个常量用以简单地模拟MM所处的状态。现在我们可以定义Subscriber类了:
struct Subscriber
{
  virtual void action(MMStatus status) = 0;
  virtual ~Subscriber() {}
};
  有经验的读者知道,这样定义Subscriber表明它是个基类,也是个抽象类,或者称之为接口(java中就有 interface关键字)。之所以这样设计,是因为可能存在很多种类型的订阅者(Subscriber接口的子类),每种订阅者对MM的同一种状态可能会有不同的处理方式。这里把多种类型的订阅者抽象出相同的接口方法,也就是Subscriber定义的第3行。第4行虽然只是个空定义,但这是不可缺少的,防止在用基类指针指向子类而delete基类指针时子类的析构行为能正确调用。我们继续定义我们的目标类。没错,就是MM情报组,我们以 MMIntelligenceAgent类表示。
class MMInteligenceAgent
{
public:
  void subscribe(Subscriber &subscriber);
  void desubscribe(Subscriber &subscriber);
private:
  void notifyAll(MMStatus status);
private:
  std::list<Subscriber*> subscribers_;
};
  MMInteligenceAgent类有两个公有方法(4、5行),分别用以增加和移除一个订阅者。订阅者可能有多个,我们选择以链表存储之(9行)。当MM状态变更后,借助notifyAll方法,链表中的所有订阅者都会得到通知。
  目标类的实现很简单:
void MMInteligenceAgent::subscribe(Subscriber &subscriber)
{
  subscribers_.push_back(&subscriber);
}

void MMInteligenceAgent::desubscribe(Subscriber &subscriber)
{
  subscribers_.erase(
  std::remove(subscribers_.begin(), subscribers_.end(), &subscriber),
  subscribers_.end());
}
void MMInteligenceAgent::notifyAll(MMStatus status)
{
  for (list<Subscriber*>::iterator it = subscribers_.begin();
  it != subscribers_.end(); ++it) {
  (*it)->action(status);
  }
}
  主要的基类及方法都写好了,接下来我们示例个具体Subscriber类:偷窃者。偷窃者一般在偷盗目标熟睡时比较容易下手,于是偷窃者可使用MM情报组提供的服务,以便知道MM何时在睡觉:
struct Larcener : public Subscriber
{
  virtual void action(MMStatus status)
  {
  if (status == Sleeping) {
  // steal something ...
  } 
  }
};
  为了使代码更完整,我们可以为MMInteligenceAgent增加一个对MM的跟踪方法,当发现MM状态改变时发出通知。
void MMInteligenceAgent::trace()
{
  ...
  MMStatus status = ...;
  notifyAll(status);
}
  最后以一个调用示例作为此篇的结束:
int main()

  ...
  Larcener l;
  MMInteligenceAgent mia;
  mia.subscribe(l);
  mia.trace();
  ...
  mia.desubscribe(l);
  ...
}

通过上篇的介绍我们知道了观察者模式的基本特点、使用场合以及如何以C++语言实现。有过多次编写观察者模式代码经验的你也许会发现,几乎所有的案例存在为数相当可观的重复性代码:定义一个观察者接口;定义一个主题并实现其诸如注册一/多个观察者,移除一/多个观察者,广播至所注册的观察者等基本行为。既然如此,我们有没有可能为所有观察者模式抽象出共有的接口与行为,以便日后复用呢?
  此篇文章便是探讨如何实现一个通用或称为万能的观察者模式库。
  我们为所有的观察者/订阅者抽象出一个共有的接口IObserver:
struct IObserver {
  virtual void update() = 0;
  virtual ~Observer() {}
};
  当主题状态发生改变时IObserver对象的update方法会被自动调用。IObserver的子类会实现update方法,以便具有其特定的行为。考虑到update方法的具体实现,大部分情况下我们需要查询主题的状态,从而做出反应。这有多种实现方案:一是生成全局或类似全局性(如Singleton技术)的主题对象:
Subject g_subject;
...
struct ConcreteObserver : public IObjserver {
  virtual void update() {
  if (g_subject.getStatus() == xxx) {
  ...
  }
};

  因为“尽可能的不要使用全局对象”缘故,这种方式不常用。二是为update方法增加一个参数,以便告知update某些必要的信息,为具有普遍性,我以Event代表此类,定义如下:
struct Event {
  Event(Subject &subject);
  BasicSubject *getSubject();
  virtual ~Event() {}
};
  很明显,这应该是个基类,所以具有需析构方法,此外,Event还提供一个获取主题的方法。BasicSubject类是我们随后要说到的主题基类。这样,IObserver接口的定义看起来应该是这样:
struct IObserver {
  virtual void update(Event &event) = 0;
  virtual ~IObserver() {}
};
  接下来处理我们的主题,根据前面所提到的它应该具有的行为,它的定义应该大致像这样:
class BasicSubject{
public:
  virtual ~BasicSubject() {}

  void addObserver(IObserver &observer);
  void removeObserver(IObserver &observer);
protected:
  void notifyAll(Event &event);
protected:
  std::list<Observer*> observers_;
};
  BasicSubject基类有三个方法,分别是增加一个观察者,移除一个观察者以及通知已注册观察者。至于其实现,我留给读者,当作练习。
  现在让我们通过以上三个基类(Event、IObserver及BasicSubject)来重新实现在上篇中所给出的例子:
struct MMEvent : public Event {
  MMEvent(MMInteligenceAgent &sub) : Event(sub) {}
};

struct MMInteligenceAgent : public BasicSubject{
  MMStatus getStatus() const {return status_;}
  void trace() {notifyAll(MMEvent(this));} // for demonstrating how to use nofifyAll method.
private:
  MMStatus status_;
};

struct Larcener : public IObserver {
  virtual void update(MMStatus status) {
  if (status == Sleeping) {
  ...
  } 
  }
};
  现在是不是简单了许多?
  不要停止你的脚步,更不要高兴的过早。
  我们事先定义了三个接口让我们的客户遵循,约束太多了。
  主题Subject与观察者Observer之间虽然已是抽象耦合(相互认识对方的接口基类),但仍可改进,使两者间的耦合度更低。
  考虑到UI中的窗口设计,需要监视的窗口事件可能有:
windowOpened
windowClosing
windowIconified
windowDeiconified
windowActivated
windowActivated
windowDeactivated
  倘若代码全由你一人设计,你大可将以上7个事件合并为一个粗事件并通过窗口(也就是这里的Subject了)提供一个标志表明目前发生的是这7个中的哪一个事件,这没什么问题。但是,我相信并不是所有代码都由你一人包办,设想你的同事或是客户将WindowEventListener(也就是这里的Observer)设计成几个独立的更新方法的情况吧(java便是如此)。糟糕,我们目前定义的IObserver接口只支持单一更新方法。
  是时候将我们的设计改进了。
  事实上,在我们定义的三个基类当中最没有意义的便是IObserver接口,它什么也没帮我们实现,仅是个Tag标记,以便我们能为BasicSubject类指明addObserver及removeObserver方法的参数。通过模板技术,我们不必定义IObserver接口:
template <
  class ObserverT,
  class ContainerT = std::list<ObserverT*>
>
class BasicSubject
{
public:
  inline void addObserver(ObserverT &observer);
  inline void removeObserver(ObserverT &observer);
protected:
  ContainerT observers_;
};
  BasicSubject不需要虚析构函数,因为客户不需要知道BasicSubject类的存在。类模板参数ContainerT的存在是为了让客户可以选择容器类型,默认容器类型是std::list,也许你的客户更喜欢std::vector,于是他便可这样使用:
  
class MyBasicSubject : public BasicSubject<MyObserver, std::vector<MyObserver*> { ...};
  当BasicSubject状态改变时需要通知观察者,所以notifyAll方法仍不可缺少。考虑到观察者可能具有多个更新方法,我们可以通过notifyAll方法的参数来指定要更新的方法。是的,就是函数指针了。所以nofifyAll方法可能是这样的:
template<typename ReturnT,typename Arg1T>
void BasicSubject::notifyAll(ReturnT (ObserverT::*pfn)(Arg1T), Arg1T arg1) {
  for (ContainerT::iterator it = observers_.begin(), itEnd = observers_.end(); it != itEnd; ++it) {
  ((*it)->*pfn)(arg1);
  }
}
  其中pfn是指向ObserverT类的、具有ReturnT返回类型的、接收一个类型为Arg1T参数的函数的指针。
  现在连Event基类都不需要了,其角色完全由模板参数类型Arg1T所取代。
  问题远没有结束。
  仔细想想Arg1T参数类型的推导,编译器既可选择从pfn函数所声明的形参类型中推导也可选择从arg1实参推导,当实参(arg1)类型可唯一推导且与pfn函数声明的形参类型完全匹配时没问题。当实参类型与形参类型不匹配时编译器报错。如:
struct MyObserver {
  void increment(int &val) {++val;}
};
struct MySubject : public BasicSubject<MyObserver> {
  void trigger() {
  int i = 10;
  notifyAll(&MyObserver::increment, i);
  }
};
  我的编译器上的报错信息大致是:"template parameter 'Arg1T' is ambiguous" ... "could be 'int' or 'int &'"。编译器不知道Arg1T是int(从实参i推导)还是int&(从函数increment形参val推导)。编译器真傻。
  此问题的根源是模板参数多渠道推导的不匹配性所致。为避免多渠道推导,聪明的你可能想到这样定义notifyAll方法:
template <typename MemFunT,typename Arg1T>
void BasicSubject::notifyAll(const MemFunT &pfn, Arg1T &arg1);
  值得表扬。
  设想pfn所声明的形参类型是const引用类型(如const int&)而用户把常量(如10)直接用作实参的情形吧:
struct MyObserver {
  void increment(const int &val) {}
};
struct MySubject : public BasicSubject<MyObserver> {
  void trigger() { 
  notifyAll(&MyObserver::increment, 10);
  }
};
  编译器会抱怨不能把实参(10)类型(int)转换到形参(val)类型(const int&)。
  那能否将arg1声明为const引用类型呢,即:
template <typename MemFunT,typename Arg1T>
void BasicSubject::notifyAll(const MemFunT &pfn, const Arg1T &arg1);
  这会限制观察者更新方法对参数进行任何修改,不可接受。
  按着你的思路,我可以给你一种解决方案,不过要将notifyAll方法声明为:
template <typename MemFunT, typename Arg1T>
inline void notifyAll(const MemFunT &pfn, Arg1T arg1) ;
  是的,arg1前少个引用(&)符号。当观察者更新方法的形参类型为非引用类型时没任何问题,仅仅是多了一次拷贝而使效率稍微低下而已:
struct MyObserver {
  void increment(int val) {}
};
struct MySubject : public BasicSubject<MyObserver> {
  void trigger() { 
  notifyAll(&MyObserver::increment, 10); // OK
  }
};
  但是当形参类型为引用类型时直接使用的结果与预期行为不符:
struct MyObserver {
  void increment(int &val) {++val;}
};
struct MySubject : public BasicSubject<MyObserver> {
  void trigger() { 
  int i = 10;
  notifyAll(&MyObserver::increment, i);
  cout << i << endl; // 输出10,但我们期望是11
  }
};
  我们可以通过一个额外的辅助类将其解决:
template <typename T>
class ref_holder
{
  T& ref_;
public:
  inline ref_holder(T& ref) : ref_(ref) {}
  inline operator T& () const {return ref_;}
};

template <typename T>
inline ref_holder<T> ByRef(T& t) {
  return ref_holder<T>(t);
}
  函数ByRef的存在仅仅是为了方便生成ref_holder对象(类似STL中的make_pair)。当需要引用传递时以ByRef函数作用到实参上:
struct MyObserver {
  void increment(int &val) {++val;}
};
struct MySubject : public BasicSubject<MyObserver> {
  void trigger() { 
  int i = 10;
  notifyAll(&MyObserver::increment, ByRef(i));
  cout << i << endl; // 输出11,OK
  }
};
  现在没问题了,前提是能正确使用。但是,我敢打赌,你的客户会经常忘记ByRef函数的存在,以致最终放弃你所提供的解决方案。
  我会给出另外一种更完美的方案。
  实际上,此处的notfiyAll方法是个转发函数,对其的调用会转发给已向BasicSubject注册了的所有观察者对象的相应更新方法(我称之为目的函数)。为了具有正确的转发行为以及较高的效率,转发函数的形参类型声明与目的函数的形参类型声明必须遵循一定的对应规则。篇幅所限,这里直接给出结论(以下将“转发函数形参”简称为“转发形参 ”,将“目的调用函数形参”简称为“目的形参”。):
目的形参类型为const引用类型时,转发形参类型也是const引用类型;
目的形参类型为non-const引用类型时,转发形参类型也是non-const引用类型;
目的形参类型为其它类型时,转发形参类型是const引用类型。
  我们通过模板traits技术可实现上面所提的转发——目的函数形参类型对应规则:
template <typename T>
struct arg_type_traits {
  typedef const T& result;
};

template <typename T>
struct arg_type_traits<T&> {
  typedef T& result;
};

template <typename T>
struct arg_type_traits<const T&> {
  typedef const T& result;
};
  最后一个traits的存在是必须的,因为引用引用类型(如int&&)在C++中是不合法的。现在我们可以定义我们的notifyAll方法了:
template <typename ReturnT, typename Arg1T>
inline void BasicSubject::notifyAll(ReturnT (ObserverT::*pfn)(Arg1T),
  typename arg_type_traits<Arg1T>::result arg1) {
  for (ContainerT::iterator it = observers_.begin(),itEnd = observers_.end(); it != itEnd; ++it)
  ((*it)->*pfn)(arg1);
}
  聪明的你可能会问,万一观察者的更新方法参数不是一个呢?说真的,我也很想确定到底具有几个参数,令我悲伤的是我的客户经常这样回答:“我也不知道有几个。”
  我使用了一种比较简单、笨拙却行之有效的手段解决了这一问题。我通过重载notifyAll方法,使其分别对应更新方法是0、1、2、3……个参数的情况。
template <typename ReturnT>
inline void BasicSubject::notifyAll(ReturnT (ObserverT::*pfn)()) {
  for (ContainerT::iterator it = observers_.begin(), itEnd = observers_.end(); it != itEnd; ++it)
  ((*it)->*pfn)();
}

template <typename ReturnT, typename Arg1T>
inline void BasicSubject::notifyAll(ReturnT (ObserverT::*pfn)(Arg1T),
  typename arg_type_traits<Arg1T>::result arg1) {
  for (ContainerT::iterator it = observers_.begin(),itEnd = observers_.end(); it != itEnd; ++it)
  ((*it)->*pfn)(arg1);
}

template <typename ReturnT, typename Arg1T, typename Arg2T>
inline void BasicSubject::notifyAll(ReturnT (ObserverT::*pfn)(Arg1T, Arg2T),
  typename arg_type_traits<Arg1T>::result arg1,
  typename arg_type_traits<Arg2T>::result arg2 ) {
  for (ContainerT::iterator it = observers_.begin(), itEnd = observers_.end(); it != itEnd; ++it)
  ((*it)->*pfn)(arg1, arg2);
}
...
template <typename ReturnT, typename Arg1T, typename Arg2T, typename Arg3T, typename Arg4T, typename Arg5T>
inline void BasicSubject::notifyAll(ReturnT (ObserverT::*pfn)(Arg1T, Arg2T, Arg3T, Arg4T, Arg5T),
  typename arg_type_traits<Arg1T>::result arg1,
  typename arg_type_traits<Arg2T>::result arg2,
  typename arg_type_traits<Arg3T>::result arg3,
  typename arg_type_traits<Arg4T>::result arg4,
  typename arg_type_traits<Arg5T>::result arg5) {
  for (ContainerT::iterator it = observers_.begin(), itEnd = observers_.end(); it != itEnd; ++it)
  ((*it)->*pfn)(arg1, arg2, arg3, arg4, arg5);
}
  按我的经验,超过5个参数的类方法不常见,要是你真的有幸遇到了,你大可让实现作者与你共进晚餐,当然,账单由他付。你也大可再为notifyAll增加几个重载方法。
  代码看起来有点复杂,但你的客户却很方便:
struct MyObserver {
  void copy(int src, int &dest) {dest = src;}
};
struct MySubject : public BasicSubject<MyObserver> {
  void trigger() { // demonstrate how to use notifyAll method.
  int i= 0;
  notifyAll(&MyObserver::copy, 100, i);
  assert(i == 100);
  }
};
int main(){
  MyObserver obs;
  MySubject sub;
  sub.addObserver(obs);
  sub.trigger(); 
}
  以上便是我所实现的通用观察者模式库的骨架。之所以称为骨架,是因为还有许多诸如多线程等现实问题没有考虑,我将在下篇中与读者一起探讨现实世界中可能遇到的问题。

我们在《设计模式之观察者(Observer)模式与其C++通用实现(中)》一文中给出了一个以C++语言实现的通用观察者模式方案骨架。然而,实际的工程项目需求往往要比理想状态复杂得多,此篇便是与读者一起探讨在现实世界中可能遇到的各种棘手问题及解决方案。

我把目前为止我所遇到的问题罗列如下:

复合主题

多线程

更新方法修改观察者链表

接下来我们一一给予讨论。

(一)复合主题

考虑GUI的组件设计,我习惯用Widget类代表之,它需要处理许多用户交互以及系统事件,其中最常见的用户交互事件有鼠标及键盘事件。倘若架构师决定以事件监听方式设计整个UI框架,那么Widget便具有主题的角色,相应的,鼠标及键盘事件便是观察者角色。实际上,一个主题对应多种(不是多个)观察者的现象很普遍。

我们借助中篇所给的观察者模式骨架实现这类应用。

借助多继承机制,很容易办到:

structMouseListener{
  voidmouseMoved(intx,inty){}
};

structKeyListener{
  voidkeyPressed(intkeyCode){}
};

classWidget:publicBasicSubject<MouseListener>,publicBasicSubject<KeyListener>{...};

添加事件监听器的伪代码大致如下:

MouseListenermel;
KeyListenerkel;
Widgetw;
w.addObserver(mel);
w.addObserver(kel);

 

 

$False$

 

 

为了使Widget添加/移除事件监听器的方法更加友好,我们可以为Widget提供addXXXListener/removeXXXListener 方法,这些方法会把调用转给基类。有了这些相对较友好的接口后,基类的addObserver/removeObserver接口对用户已经没有用了,所以我们可改用protected继承。综合起来,代码看起来大致像这样:

classWidget:protectedBasicSubject<MouseListener>,protectedBasicSubject<KeyListener>{
  typedefBasicSubject<MouseListener>MouseSubject;
  typedefBasicSubject<KeyListener>KeySubject;
public:
  inlinevoidaddMouseListener(MouseListener&mel){
    MouseSubject::addObserver(mel);
  }

  inlinevoidremoveMouseListener(MouseListener&mel){
    MouseSubject::removeObserver(mel);
  }

  inlinevoidaddKeyListener(KeyListener&kel){
    KeySubject::addObserver(kel);
  }

  inlinevoidremoveKeyListener(KeyListener&kel){
    KeySubject::removeObserver(kel);
  }

  voidhandleMsg(intmsg){
    if(msg==0){
      MouseSubject::notifyAll(&MouseListener::mouseMoved,1,1);
    }elseif(msg==1){
      KeySubject::notifyAll(&KeyListener::keyPressed,100);
    }
  }
};

当然,你也可以不使用继承改而使用组合技术实现,这完全取决于你的爱好。组合版本的实现大致是像这样的:

classWidget {
public:
  inlinevoidaddMouseListener(MouseListener&mel){
    ms_.addObserver(mel);
  }

  inlinevoidremoveMouseListener(MouseListener&mel){
    ms_.removeObserver(mel);
  }
  ... 
private:
  BasicSubject<MouseListener> ms_;
  BasicSubject<KeyListener> ks_;
};

 

 

 

(二)多线程

倘若我们的应用程序运行在多线程环境中,那你可要谨慎了。试想线程A正在添加观察者的同时另一线程B也试图添加观察者吧。我们默认使用的容器std::list是线程非安全的,所以我们的BasicSubjcet也会是线程非安全的。要解决此问题,有两种途径。一是使用线程安全容器,另一种是我们在BasicSubject的适当地方放置锁。我只讨论后一种情况。

为了让代码具有一定的灵活性,我们使用泛型编程中常用的Policies技术。第一步将锁类定义出来:

structNullLocker{
  inlinevoidlock(){};
  inlinevoidunlock(){};
};

structCriticalSectionLocker{
  CriticalSectionLocker(){::InitializeCriticalSection(&cs_);}
  ~CriticalSectionLocker(){::DeleteCriticalSection(&cs_);}
  inlinevoidlock(){::EnterCriticalSection(&cs_);}
  inlinevoidunlock(){::LeaveCriticalSection(&cs_);}
private:
  CRITICAL_SECTIONcs_;
};

前者为空锁,用于单线程环境中。后者借助Windows平台中的临界区实现进程内的锁语义。你也可以再增加进程间的锁语义。

接着便是将我们的BasicSubject类修改成如下样子:

template<
  classObserverT,
  classLockerT=NullLocker,
  classContainerT=std::list<ObserverT*>
>
classBasicSubject:protectedLockerT{
public:
  inlinevoidaddObserver(ObserverT&observer){
    lock();
    observers_.push_back(&observer);
    unlock();
  }

  inlinevoidremoveObserver(ObserverT&observer){
    lock();
    ...
    unlock();
  }

protected:
  template<typenameReturnT>
  inlinevoidnotifyAll(ReturnT(ObserverT::*pfn)()) {
    lock();
    for(ContainerT::iteratorit=observers_.begin(),itEnd=observers_.end();it!=itEnd;++it)
      ((*it)->*pfn)();
    unlock();
  }
  ...
};

默认的锁类是NullLocker,也就是运行在单线程环境中。需要工作在多线程中时可像这样使用:

classWidget:protectedBasicSubject<MouseListener,CriticalSectionLocker>{...};

 

 

 

(三)更新方法修改观察者链表

想像一下当观察者在接收到通知而立即修改主题中的观察者链表时会发生什么?因为主题是通过对已注册的观察者链表迭代而逐个通知观察者的相应更新方法的,换句话说,在迭代进行中观察者就去修改观察者链表。这个问题类似于这样的代码设计:

std::list<int>is = ...
for(std::list<int>::iteratorit=is.begin();it!=is.end();++it){
  is.erase(std::remove(is.begin(),is.end(),2), is.end());
}

危险!迭代器在链表被修改后有可能失效。

也许你会疑虑,在使用了(二)中所提的锁机制之后不就不会有此问题了吗?实际情况是,锁对于此类问题没有任何作用。

解决此类问题的最好办法是使用不会因容器本身被修改而促使迭代器失效的容器。然而,就目前来说,标准STL库中的所有容器都不属此类。因此,我们有必要花点心思处理此类问题。

当链表处于被迭代过程中时,对链表的修改动作先被记录下来,等到链表迭代完毕后再回过头执行先前记录下来的修改动作,如果对链表的修改动作不是发生在迭代过程中,就按普通方式处理。依据此思想,代码可像这样实现:

template<
  ...
>
classBasicSubject:protectedLockerT
{
public:
  BasicSubject():withinLoop_(false){}

  voidaddObserver(ObserverT&observer){
    lock();
    if(withinLoop_)
      modifyActionBuf_.insert(std::make_pair(true,&observer));
    else
      observers_.push_back(&observer);
    unlock();
  }

  voidremoveObserver(ObserverT&observer){
    lock();
    if(withinLoop_)
      modifyActionBuf_.insert(std::make_pair(false,&observer));
    else
      observers_.erase(
        remove(observers_.begin(),observers_.end(),&observer),
        observers_.end());
    unlock();
  }

protected: 
  template<typenameReturnT>
  voidnotifyAll(ReturnT(ObserverT::*pfn)()) {
    lock();
    beginLoop();
    for(ContainerT::iteratorit=observers_.begin(),itEnd=observers_.end();it!=itEnd;++it)
      ((*it)->*pfn)();
    endLoop();
    unlock();
  }
  ...
private:
  inlinevoidbeginLoop(){
    withinLoop_=true;
  }

  voidendLoop(){
    if(!modifyActionBuf_.empty()){
      for(std::multimap<bool,ObserverT*>::iteratorit=modifyActionBuf_.begin(),
        itEnd=modifyActionBuf_.end();it!=itEnd;++it){
          if(it->first)
            observers_.push_back(it->second);
          else
            observers_.erase(
              remove(observers_.begin(),observers_.end(),it->second),
              observers_.end());
      }
      modifyActionBuf_.clear();
    }
    withinLoop_=false;
  }

protected:
  ContainerTobservers_;

private:
  boolwithinLoop_;
  std::multimap<bool,ObserverT*>modifyActionBuf_;
};

我使用了STL中的multimap模板类来储存修改动作。其中key被设为bool类型,true表明是添加动作,false表明是移除动作。此外,因代码量的增加,内联函数已无必要,故移除了所有的inline关键字。

后记:编写通用库时不能假定用户所处某一特定环境中,因而须谨慎应对各种可能遇到的问题,这便是为什么我们常说库的实现往往比为特定应用而编写的模块要复杂得多的缘故,加之C++语言本身的复杂性以及局限性,以致我们设计一个相对完美的观察者模式是何其困难。

鉴于以上情况,我相信问题远不止如此,真诚希望读者提出你所遇到的各种问题,以便我们一起讨论学习。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/kingmax26/archive/2008/10/06/3024576.aspx

原创粉丝点击