当前位置:天才代写 > tutorial > C语言/C++ 教程 > 设计模式之调查者(Observer)模式与其C++通用实现(下)

设计模式之调查者(Observer)模式与其C++通用实现(下)

2017-11-04 08:00 星期六 所属: C语言/C++ 教程 浏览:718

副标题#e#

我们在《设计模式之调查者(Observer)模式与其C++通用实现(中)》一文中给出了一个以C++语言实现的通用调查者模式方案骨架。然而,实际的工程项目需求往往要比抱负状态巨大得多,此篇即是与读者一起探讨在现实世界中大概碰着的各类棘手问题及办理方案。

我把今朝为止我所碰着的问题摆列如下:

复合主题

多线程

更新要领修改调查者链表

接下来我们一一给以接头。

(一)复合主题

思量GUI的组件设计,我习习用Widget类代表之,它需要处理惩罚很多用户交互以及系统事件,个中最常见的用户交互事件有鼠标及键盘事件。倘若架构师抉择以事件监听方法设计整个UI框架,那么Widget便具有主题的脚色,相应的,鼠标及键盘事件即是调查者脚色。实际上,一个主题对应多种(不是多个)调查者的现象很普遍。

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

借助多担任机制,很容易办到:

01.struct MouseListener {
02. void mouseMoved(int x, int y) {}
03.};
04.
05.struct KeyListener {
06. void keyPressed(int keyCode) {}
07.};
08.
09.class Widget : public BasicSubject<MouseListener>, public BasicSubject<KeyListener>{...};

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

01.MouseListener mel;
02.KeyListener kel;
03.Widget w;
04.w.addObserver(mel);
05.w.addObserver(kel);


#p#副标题#e#

为了使Widget添加/移除事件监听器的要领越发友好,我们可觉得Widget提供addXXXListener/removeXXXListener 要领,这些要了解把挪用转给基类。有了这些相对较友好的接口后,基类的addObserver/removeObserver接口对用户已经没有用了,所以我们可改用protected担任。综合起来,代码看起来大抵像这样:

01.class Widget : protected BasicSubject<MouseListener>,protected BasicSubject<KeyListener>{
02. typedef BasicSubject<MouseListener> MouseSubject;
03. typedef BasicSubject<KeyListener> KeySubject;
04.public:
05. inline void addMouseListener(MouseListener &mel) {
06. MouseSubject::addObserver(mel);
07. }
08.
09. inline void removeMouseListener(MouseListener &mel) {
10. MouseSubject::removeObserver(mel);
11. }
12.
13. inline void addKeyListener(KeyListener &kel) {
14. KeySubject::addObserver(kel);
15. }
16.
17. inline void removeKeyListener(KeyListener &kel) {
18. KeySubject::removeObserver(kel);
19. }
20.
21. void handleMsg(int msg) {
22. if (msg == 0) {
23. MouseSubject::notifyAll(&MouseListener::mouseMoved, 1, 1);
24. } else if (msg == 1) {
25. KeySubject::notifyAll(&KeyListener::keyPressed, 100);
26. }
27. }
28.};

虽然,你也可以不利用担任改而利用组合技能实现,这完全取决于你的喜好。组合版本的实现大抵是像这样的:

01.class Widget {
02.public:
03. inline void addMouseListener(MouseListener &mel) {
04. ms_.addObserver(mel);
05. }
06.
07. inline void removeMouseListener(MouseListener &mel) {
08. ms_.removeObserver(mel);
09. }
10. ...
11.private:
12. BasicSubject<MouseListener> ms_;
13. BasicSubject<KeyListener> ks_;
14.};

#p#副标题#e#

(二)多线程

倘若我们的应用措施运行在多线程情况中,那你可要审慎了。试想线程A正在添加调查者的同时另一线程B也试图添加调查者吧。我们默认利用的容器std::list是线程非安详的,所以我们的BasicSubjcet也会是线程非安详的。要办理此问题,有两种途径。一是利用线程安详容器,另一种是我们在BasicSubject的适本处所安排锁。我只接头后一种环境。

为了让代码具有必然的机动性,我们利用泛型编程中常用的Policies技能。第一步将锁类界说出来:

01.struct NullLocker{
02. inline void lock() {};
03. inline void unlock() {};
04.};
05.
06.struct CriticalSectionLocker{
07. CriticalSectionLocker() {::InitializeCriticalSection(&cs_);}
08. ~CriticalSectionLocker() {::DeleteCriticalSection(&cs_);}
09. inline void lock() {::EnterCriticalSection(&cs_);}
10. inline void unlock() {::LeaveCriticalSection(&cs_);}
11.private:
12. CRITICAL_SECTION cs_;
13.};

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

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

01.template <
02. class ObserverT,
03. class LockerT = NullLocker,
04. class ContainerT = std::list<ObserverT *>
05.>
06.class BasicSubject : protected LockerT {
07.public:
08. inline void addObserver(ObserverT &observer) {
09. lock();
10. observers_.push_back(&observer);
11. unlock();
12. }
13.
14. inline void removeObserver(ObserverT &observer) {
15. lock();
16. ...
17. unlock();
18. }
19.
20.protected:
21. template <typename ReturnT>
22. inline void notifyAll(ReturnT (ObserverT::*pfn)()) {
23. lock();
24. for (ContainerT::iterator it = observers_.begin(), itEnd = observers_.end(); it != itEnd; ++it)
25. ((*it)->*pfn)();
26. unlock();
27. }
28. ...
29.};

默认的锁类是NullLocker,也就是运行在单线程情况中。需要事情在多线程中时可像这样利用:

class Widget : protected BasicSubject<MouseListener, CriticalSectionLocker> {…};

#p#副标题#e#

(三)更新要领修改调查者链表

#p#分页标题#e#

想像一下当调查者在吸收到通知而当即修改主题中的调查者链表时会产生什么?因为主题是通过对已注册的调查者链表迭代而逐个通知调查者的相应更新要领的,换句话说,在迭代举办中调查者就去修改调查者链表。这个问题雷同于这样的代码设计:

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

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

也许你会疑虑,在利用了(二)中所提的锁机制之后不就不会有此问题了吗?实际环境是,锁对付此类问题没有任何浸染。

办理此类问题的最好步伐是利用不会因容器自己被修改而促使迭代器失效的容器。然而,就今朝来说,尺度STL库中的所有容器都不属此类。因此,我们有须要花点心思处理惩罚此类问题。

当链表处于被迭代进程中时,对链表的修转业动先被记录下来,比及链表迭代完毕后再回过甚执行先前记录下来的修转业动,假如对链表的修转业动不是产生在迭代进程中,就按普通方法处理惩罚。依据此思想,代码可像这样实现:

01.template <
02. ...
03.>
04.class BasicSubject : protected LockerT
05.{
06.public:
07. BasicSubject() : withinLoop_(false) {}
08.
09. void addObserver(ObserverT &observer) {
10. lock();
11. if (withinLoop_)
12. modifyActionBuf_.insert(std::make_pair(true, &observer));
13. else
14. observers_.push_back(&observer);
15. unlock();
16. }
17.
18. void removeObserver(ObserverT &observer) {
19. lock();
20. if (withinLoop_)
21. modifyActionBuf_.insert(std::make_pair(false, &observer));
22. else
23. observers_.erase(
24. remove(observers_.begin(), observers_.end(), &observer),
25. observers_.end());
26. unlock();
27. }
28.
29.protected:
30. template <typename ReturnT>
31. void notifyAll(ReturnT (ObserverT::*pfn)()) {
32. beginLoop();
33. for (ContainerT::iterator it = observers_.begin(), itEnd = observers_.end(); it != itEnd; ++it)
34. ((*it)->*pfn)();
35. endLoop();
36. }
37. ...
38.private:
39. inline void beginLoop() {
40. lock();
41. withinLoop_ = true;
42. unlock();
43.
44. }
45.
46. void endLoop() {
47. lock();
48. if (!modifyActionBuf_.empty()) {
49. for (std::multimap<bool, ObserverT*>::iterator it = modifyActionBuf_.begin(),
50. itEnd = modifyActionBuf_.end(); it != itEnd; ++it) {
51. if (it->first)
52. observers_.push_back(it->second);
53. else
54. observers_.erase(
55. remove(observers_.begin(), observers_.end(), it->second),
56. observers_.end());
57. }
58. modifyActionBuf_.clear();
59. }
60. withinLoop_ = false;
61. unlock();
62. }
63.
64.protected:
65. ContainerT observers_;
66.
67.private:
68. bool withinLoop_;
69. std::multimap<bool, ObserverT*> modifyActionBuf_;
70.};

我利用了STL中的multimap模板类来储存修转业动。个中key被设为bool范例,true表白是添加行动,false表白是移除行动。另外,因代码量的增加,内联函数已无须要,故移除了所有的inline要害字。

#p#分页标题#e#

跋文:编写通用库时不能假定用户所处某一特定情况中,因而须审慎应对各类大概碰着的问题,这即是为什么我们常说库的实现往往比为特定应用而编写的模块要巨大得多的缘故,加之C++语言自己的巨大性以及范围性,乃至我们设计一个相对完美的调查者模式是何其坚苦。

鉴于以上环境,我相信问题远不止如此,真诚但愿读者提出你所碰着的各类问题,以便我们一起接头进修。

<暂完>

 

    关键字:

天才代写-代写联系方式