用户操作
[即时聊天] [发私信] [加为好友]
林石ID:carylin
57965次访问,排名1904,好友98人,关注者119人。
享受自然,喜爱运动;努力工作学习。现供职于MSI ELECTRONICS(KUNSHAN)CO.,LTD CE产品部,从事消费性电子产品软件开发工作。对嵌入式产品及服务器管理软件具有开发经验。
carylin的文章
原创 7 篇
翻译 1 篇
转载 0 篇
评论 25 篇
最近评论
cp:也太狭隘了吧,一看题目还以为真的是项目管理的内容
这些内容应该是研发经理考虑的事情
qq84937231:有道理
qb:其实那个循环也不是必须被放在同步块。因为在beginLoop()中 withinLoop_被设置,那么addObserver和
removeObserver就不会修改observers_
holon:不错,支持一下

------------------------------
www.arraylist.cn cn域名免费送
IT人的酒吧式交流平台
-----------------------------
thesameway:51旧书网 同城易书
www.51jiushu.com
www.51jiushu.net
二手书、旧书同城交易平台
分类齐全、快速发布、准确搜索
文章分类
收藏
    相册
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 设计模式之观察者模式与其C++通用实现(下)收藏

    新一篇: 项目管理者必须具有的观念 | 旧一篇: 设计模式之观察者模式与其C++通用实现(中)


        设计模式之观察者(Observer)模式与其C++通用实现分上、中、下三篇。上篇详细讲解何为观察者模式以及其特点,并给出一个应用实例与其实现。中篇研究如何运用C++各种技术实现一个通用/万能的观察者模式。下篇讨论中篇所给出的实现可能遇到的问题及解决方案。

    设计模式之观察者(Observer)模式与其C++通用实现(下)
                                                                            ——林石 2008-10-08

        我们在《设计模式之观察者(Observer)模式与其C++通用实现(中)》一文中给出了一个以C++语言实现的通用观察者模式方案骨架。然而,实际的工程项目需求往往要比理想状态复杂得多,此篇便是与读者一起探讨在现实世界中可能遇到的各种棘手问题及解决方案。
        我把目前为止我所遇到的问题罗列如下:
    • 复合主题
    • 多线程
    • 更新方法修改观察者链表
        接下来我们一一给予讨论。

        (一)复合主题

        考虑GUI的组件
    设计,我习惯用Widget类代表之,它需要处理许多用户交互以及系统事件,其中最常见的用户交互事件有鼠标及键盘事件。倘若架构师决定以事件监听方式设计整个UI框架,那么Widget便具有主题的角色,相应的,鼠标及键盘事件便是观察者角色。实际上,一个主题对应多种(不是多个)观察者的现象很普遍。
        我们借助中篇所给的观察者模式骨架实现这类应用。
        借助多继承机制,很容易办到:
    1. struct MouseListener {
    2.     void mouseMoved(int x, int y) {}
    3. };

    4. struct KeyListener {
    5.     void keyPressed(int keyCode) {}
    6. };

    7. class Widget : public BasicSubject<MouseListener>, public BasicSubject<KeyListener>{...};
        添加事件监听器的伪代码大致如下:
    1. MouseListener mel;
    2. KeyListener kel;
    3. Widget w;
    4. w.addObserver(mel);
    5. w.addObserver(kel);
        为了使Widget添加/移除事件监听器的方法更加友好,我们可以为Widget提供addXXXListener/removeXXXListener 方法,这些方法会把调用转给基类。有了这些相对较友好的接口后,基类的addObserver/removeObserver接口对用户已经没有用了,所以我们可改用protected继承。综合起来,代码看起来大致像这样:
    1. class Widget : protected BasicSubject<MouseListener>,protected BasicSubject<KeyListener>{
    2.     typedef BasicSubject<MouseListener> MouseSubject;
    3.     typedef BasicSubject<KeyListener> KeySubject;
    4. public:
    5.     inline void addMouseListener(MouseListener &mel) {
    6.         MouseSubject::addObserver(mel);
    7.     }

    8.     inline void removeMouseListener(MouseListener &mel) {
    9.         MouseSubject::removeObserver(mel);
    10.     }  

    11.     inline void addKeyListener(KeyListener &kel) {
    12.         KeySubject::addObserver(kel);
    13.     }

    14.     inline void removeKeyListener(KeyListener &kel) {
    15.         KeySubject::removeObserver(kel);
    16.     }

    17.     void handleMsg(int msg) {
    18.         if (msg == 0) {
    19.             MouseSubject::notifyAll(&MouseListener::mouseMoved, 1, 1);
    20.         } else if (msg == 1) {
    21.             KeySubject::notifyAll(&KeyListener::keyPressed, 100);
    22.         }       
    23.     }
    24. };
        当然,你也可以不使用继承改而使用组合技术实现,这完全取决于你的爱好。组合版本的实现大致是像这样的:
    1. class Widget {
    2. public:
    3.     inline void addMouseListener(MouseListener &mel) {
    4.         ms_.addObserver(mel);
    5.     }

    6.     inline void removeMouseListener(MouseListener &mel) {
    7.         ms_.removeObserver(mel);
    8.     }
    9.     ... 
    10. private:
    11.     BasicSubject<MouseListener> ms_;
    12.     BasicSubject<KeyListener> ks_;
    13. };
       
        (二)多线程


        倘若我们的应用程序运行在多线程环境中,那你可要谨慎了。试想线程A正在添加观察者的同时另一线程B也试图添加观察者吧。我们默认使用的容器std::list是线程非安全的,所以我们的BasicSubjcet也会是线程非安全的。要解决此问题,有两种途径。一是使用线程安全容器,另一种是我们在BasicSubject的适当地方放置锁。我只讨论后一种情况。
        为了让代码具有一定的灵活性,我们使用泛型编程中常用的Policies技术。第一步将锁类定义出来:
    1. struct NullLocker{
    2.     inline void lock() {};
    3.     inline void unlock() {};
    4. };

    5. struct CriticalSectionLocker{
    6.     CriticalSectionLocker() {::InitializeCriticalSection(&cs_);}
    7.     ~CriticalSectionLocker() {::DeleteCriticalSection(&cs_);}
    8.     inline void lock() {::EnterCriticalSection(&cs_);}
    9.     inline void unlock() {::LeaveCriticalSection(&cs_);}
    10. private:
    11.     CRITICAL_SECTION cs_;
    12. };
        前者为空锁,用于单线程环境中。后者借助Windows平台中的临界区实现进程内的锁语义。你也可以再增加进程间的锁语义。
        接着便是将我们的BasicSubject类
    修改成如下样子:
    1. template <
    2.     class ObserverT,
    3.     class LockerT = NullLocker,
    4.     class ContainerT = std::list<ObserverT *>
    5. >
    6. class BasicSubject : protected LockerT {
    7. public:
    8.     inline void addObserver(ObserverT &observer) {
    9.         lock();
    10.         observers_.push_back(&observer);
    11.         unlock();
    12.     }

    13.     inline void removeObserver(ObserverT &observer) {
    14.         lock();
    15.         ...
    16.         unlock();
    17.     }

    18. protected:
    19.     template <typename ReturnT>
    20.     inline void notifyAll(ReturnT (ObserverT::*pfn)())  {
    21.         lock();
    22.         for (ContainerT::iterator it = observers_.begin(), itEnd = observers_.end(); it != itEnd; ++it)
    23.             ((*it)->*pfn)();
    24.         unlock();
    25.     }  
    26.     ...
    27. };
        默认的锁类是NullLocker,也就是运行在单线程环境中。需要工作在多线程中时可像这样使用:
    1. class Widget : protected BasicSubject<MouseListener, CriticalSectionLocker> {...};

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

        想像一下当观察者在接收到通知而立即修改主题中的观察者链表时会发生什么?因为主题是通过对已注册的观察者链表迭代而逐个通知观察者的相应更新方法的,换句话说,在迭代进行中观察者就去修改观察者链表。这个问题类似于这样的代码设计:
    1. std::list<int> is = ...
    2. for (std::list<int>::iterator it = is.begin(); it != is.end(); ++it) {
    3.     is.erase(std::remove(is.begin(), is.end(), 2), is.end());
    4. }
        危险!迭代器在
    链表被修改后有可能失效。
        也许你会疑虑,在使用了(二)中所提的锁机制之后不就不会有此问题了吗?实际情况是,锁对于此类问题没有任何作用。
        解决此类问题的最好办法是使用不会因容器本身被修改而促使
    迭代器失效的容器。然而,就目前来说,标准STL库中的所有容器都不属此类。因此,我们有必要花点心思处理此类问题。   
        当链表处于被迭代过程中时,对链表的修改动作先被记录下来,等到链表迭代完毕后再回过头执行先前记录下来的修改动作,如果对链表的修改动作不是发生在迭代过程中,就按普通方式处理。依据此思想,代码可像这样实现:
    1. template <
    2.     ...
    3. >
    4. class BasicSubject : protected LockerT
    5. {
    6. public:
    7.     BasicSubject() : withinLoop_(false) {}

    8.     void addObserver(ObserverT &observer) {
    9.         lock();
    10.         if (withinLoop_) 
    11.             modifyActionBuf_.insert(std::make_pair(true, &observer));
    12.         else
    13.             observers_.push_back(&observer);        
    14.         unlock();
    15.     }

    16.     void removeObserver(ObserverT &observer) {
    17.         lock();
    18.         if (withinLoop_)
    19.             modifyActionBuf_.insert(std::make_pair(false, &observer));
    20.         else
    21.             observers_.erase(
    22.                 remove(observers_.begin(), observers_.end(), &observer),
    23.                 observers_.end());
    24.         unlock();
    25.     }

    26. protected:  
    27.     template <typename ReturnT>
    28.     void notifyAll(ReturnT (ObserverT::*pfn)())  {
    29.         lock();
    30.         beginLoop();
    31.         for (ContainerT::iterator it = observers_.begin(), itEnd = observers_.end(); it != itEnd; ++it)
    32.             ((*it)->*pfn)();
    33.         endLoop();
    34.         unlock();
    35.     }
    36.     ...
    37. private:
    38.     inline void beginLoop() {
    39.         withinLoop_ = true;
    40.     }

    41.     void endLoop() {
    42.         if (!modifyActionBuf_.empty()) {
    43.             for (std::multimap<bool, ObserverT*>::iterator it = modifyActionBuf_.begin(),
    44.                 itEnd = modifyActionBuf_.end(); it != itEnd; ++it) {
    45.                     if (it->first)
    46.                         observers_.push_back(it->second);
    47.                     else
    48.                         observers_.erase(
    49.                             remove(observers_.begin(), observers_.end(), it->second),
    50.                             observers_.end());
    51.             }
    52.             modifyActionBuf_.clear();
    53.         }       
    54.         withinLoop_ = false;
    55.     }

    56. protected:
    57.     ContainerT observers_;

    58. private:
    59.     bool withinLoop_;
    60.     std::multimap<bool, ObserverT*> modifyActionBuf_;
    61. };
        我使用了STL中的multimap模板类来储存修改动作。其中key被设为bool类型,true表明是添加动作,false表明是移除动作。此外,因代码量的增加,内联函数已无必要,故移除了所有的inline关键字。

        后记:编写通用库时不能假定用户所处某一特定环境中,因而须谨慎应对各种可能遇到的问题,这便是为什么
    我们常说库的实现往往比为特定应用而编写的模块要复杂得多的缘故,加之C++语言本身的复杂性以及局限性,以致我们设计一个相对完美的观察者模式是何其困难。
        鉴于以上情况,我相信问题远不止如此,真诚希望读者提出你所遇到的各种问题,以便我们一起讨论学习。

        <暂完>

    发表于 @ 2008年10月08日 19:37:00|评论(loading...)|收藏

    新一篇: 项目管理者必须具有的观念 | 旧一篇: 设计模式之观察者模式与其C++通用实现(中)

    评论

    #creative55 发表于2008-10-09 08:57:18  IP: 218.4.63.*
    顶一个。
    #qb 发表于2008-10-09 14:51:38  IP: 219.142.178.*
    其实你的当中还有一个小的漏洞,就是要就锁必须是递归锁,要不然,在收到通知时修改观察者链表会死锁。
    修改方法我认为可以在notifyAll不获取锁而只需要beginLoop和endLoop中获取锁
    2008-10-09 17:46:16作者回复
    pb兄果然看得细心。没错,我们现在的方案有个限制,那就是必须是递归锁。其实按你所说的“在notifyAll不获取锁而只需要beginLoop和endLoop中获取锁”似乎也不能解决问题。因为不管怎样,for循环(for (ContainerT::iterator it = observers_.begin(), itEnd = ...)必须得放置到同步块内(也就是需要被锁住的意思),所以unlock同样会被多次调用。就目前的系统,都会有递归锁,所以这不是什么问题。假若你所实现的系统没有递归锁,我们可以将非递归锁包装成递归锁。这是Windows平台的包装方法之一:
    template<class NonrecursiveLockerT>
    struct RecursiveLocker{
    RecursiveLocker() : count_(0) {}

    void lock() {
    if (InterlockedIncrement(&count_) == 1L)
    nrl_.lock();
    }

    void unlock() {
    if (InterlockedDecrement(&count_) == 0L)
    nrl_.unlock();
    }
    private:
    volatile long count_;
    NonrecursiveLockerT nrl_;
    };
    #Solstice 发表于2008-10-12 23:15:42  IP: 222.65.68.*
    如果回调的过程中,对象(在另一个线程里)析构咋办?会不会直接core dump?
    2008-10-13 08:08:56作者回复
    非常可能。解决办法之一是在观察者的析构方法中自动断开与主题的链接关系,即在观察者析构方法中调用BasicSubject::removeObserver(this)。解决方法之二是,使用smart_ptr。反正,由于C/C++没垃圾回收机制,无效指针一直是个比较棘手的问题。
    #qb 发表于2008-10-15 17:14:29  IP: 219.142.178.*
    其实那个循环也不是必须被放在同步块。因为在beginLoop()中 withinLoop_被设置,那么addObserver和
    removeObserver就不会修改observers_
    发表评论  


    登录
    Csdn Blog version 3.1a
    Copyright © 林石