定义

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

   Observer模式描述了如何建立这种关系。这一模式中的关键对象是目标(subject)和观察者(observer)。一个目标可以有任意数目的依赖它的观察者。一旦目标的状态发生改变,所有的观察者都得到通知。这种交互也称为发布-订阅(publish-subscribe)。目标是通知的发布者。故观察者模式又名 依赖(Dependents),发布-订阅(Publish-Subscribe)。

 适用场景

以下任何一种情况可以使用观察者模式:

  • 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这两者封装在独立的对象中以使它们可以各自独立地改变和复用。
  • 当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象有待改变。
  • 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之,你不希望这些对象是紧密耦合的。

代码示例

  观察者模式,首先需要定义观察者基类(接口)Observer和目标基类(接口)Subject,观察者基类需要对外提供一个接口函数Update,Update是一个纯虚函数,方便多态的实现,供目标通知观察者更新。

  1. class Subject;
  2.  
  3. class Observer{
  4. public:
  5. virtual ~Observer();
  6. virtual void Update(Subject* theChangedSubject) = ;
  7. protected:
  8. Observer();
  9. };

  目标基类需要提供支持观察者订阅函数Attach;观察者取消订阅函数Detach;对已订阅的所有观察者进行广播的函数Notify,在目标变化后,该函数遍历通知所有已订阅的观察者;以及用于存储已订阅该目标的所有观察者的列表结构List

  1. class Subject{
  2. public:
  3. virtual ~Subject();
  4.  
  5. virtual void Attach(Observer *);
  6. virtual void Detach(Observer *);
  7. virtual void Notify();
  8. protected:
  9. Subject();
  10. private:
  11. List<Observer *> *_observers;
  12. };
  13.  
  14. void Subject::Attach(Observer* o){
  15. _observers->Append(o);
  16. }
  17.  
  18. void Subject::Detach(Observer *o){
  19. _observers->Remove(o);
  20. }
  21.  
  22. void Subject::Notify(){
  23. ListIterator<Observer*> i(_observers);
  24. for(i.First(); !i.IsDone(); i.Next()){
  25. i.CurrentItem->Update(this);
  26. }
  27. }

     基于目标接口类Subject和观察者接口类Observer,下面是GOF写的设计模式一书中的观察者模式应用——一个简单的时钟,每当定时器滴答一下,定时器(目标)就会通知时钟(观察者)绘制界面更新显示。

  ClockTimer是基于目标接口类实现的目标实例,GetHour、GetMinute、GetSecond分别用于获取时、分、秒,Tick函数为计时器实现函数,每过一个滴答时刻,该函数就会调用目标基类的Notify函数通知所有已订阅观察者对象。

  1. class ClockTimer::public Subject{
  2. public:
  3. ClockTimer();
  4.  
  5. virtual int GetHour();
  6. virtual int GetMinute();
  7. virtual int GetSecond();
  8.  
  9. void Tick();
  10. };
  11.  
  12. void ClockTimer::Tick(){
  13. //update internal time-keeping state
  14. //...
  15. Notify();
  16. }

  DigitalClock继承Widget和Observer,实现观察者实例。构造函数中初始化目标实现类ClockTimer,并订阅当前观察者对象;析构函数中取消订阅当前观察者对象;Update函数是观察者基类纯虚函数的真正实现,当目标ClockTimer更新,调用Notify时,会调用到当前对象Update函数,在Update函数中再调用Draw更新时钟界面显示。

  1. class DigitalClock::public Widget,public Observer{
  2. public:
  3. DigitalClock(ClockTimer *);
  4. virtual ~DigitalClock();
  5.  
  6. virtual void Update(Subject *);
  7. //overrides Observer operation
  8.  
  9. virtual void Draw();
  10. //overrides Widget operation;
  11. //defines how to draw the digital clock
  12. private:
  13. ClockTimer* _subject;
  14. };
  15.  
  16. DigitalClock::DigitalClock(ClockTimer *s){
  17. _subject = s;
  18. _subject->Attach(this);
  19. }
  20.  
  21. DigitalClock::~DigitalClock(){
  22. _subject->Detach(this);
  23. }
  24.  
  25. void DigitalClock::Update(Subject * theChangedSubject){
  26. if(theChangedSubject == _subject){
  27. Draw();
  28. }
  29. }
  30.  
  31. void DigitalClock::Draw(){
  32. //get the new values from the subject
  33.  
  34. int hour = _subject->GetHour();
  35. int minute = _subject->GetMinute();
  36. //etc.
  37.  
  38. //draw the digital clock
  39. }

  有了上述观察者和目标的实现类后,就能实现基于观察者设计模式的数字时钟实现,code为:

  1. ClockTimer *timer = new ClockTimer;//新建目标对象
  2. DigitalClock *digitalClock = new DigitalClock(timer);//新建观察者对象并订阅ClockTimer目标对象

  上例是目标-观察者一 一对应的示例,比如实现多个相同时间的数字时钟,这时会用到单目标-多观察者模型:

  1. ClockTimer *timer = new ClockTimer;
  2. DigitalClock *digitalClock1 = new DigitalClock(timer);//Observer 1
  3. DigitalClock *digitalClock2 = new DigitalClock(timer);//Observer 2
  4. .
  5. .
  6. .
  7. DigitalClock *digitalClockn = new DigitalClock(timer);//Observer n

    当要实现监听多个端口的TCP连接的程序,这时会用到多目标-单观察者模型,需要在上ClockTimer和DigitalClock的基础上稍作修改,将观察者类中的目标用二维指针来存储目标对象,目标类、观察者类以及应用代码如下:

  目标类:

  1. class ClientTCPConnect::public Subject{
  2. public:
  3. TcpConnect();
  4.  
  5. virtual void OnReceivedData();//Received Data
  6.  
  7. virtual void GetData();
  8. };
  9.  
  10. void ClientTCPConnect::OnReceivedData(){
  11. //...
  12. Notify();
  13. }
  14.  
  15. void ClientTCPConnect::GetData(){
  16. //Get TCP Data
  17. }

  观察者类:

     ServerTCPConnect构造函数中传入目标者类的指针和大小,并订阅所有目标者类。

  1. class ServerTCPConnect::public Observer{
  2. public:
  3. ServerTCPConnect(ClientTCPConnect **, int size);
  4.  
  5. virtual ~ServerTCPConnect();
  6.  
  7. virtual void Update(Subject *);
  8. //overrides Observer operation
  9.  
  10. virtual void DealClientConnect(Subject *);
  11.  
  12. private:
  13. ClientTCPConnect **_sbuject;
  14. int _size;
  15.  
  16. };
  17.  
  18. ServerTCPConnect::ServerTCPConnect(ClientTCPConnect**s, int size){
  19. _subject = s;
  20. for(int i = ; i < _size; i++){
  21. _subject[i]->Attach(this);
  22. }
  23. }
  24.  
  25. ServerTCPConnect::~ServerTCPConnect(){
  26. for(int i = ; i < _size; i++){
  27. _subject[i]->Detach(this);
  28. }
  29. }
  30.  
  31. void ServerTCPConnect::Update(Subject * theChangedSubject){
  32. for(int i = ; i < _size; i++){
  33. if(theChangedSubject == _subject[i]){
  34. DealClientConnect(_subject[i]);
  35. }
  36. }
  37. }
  38.  
  39. void ServerTCPConnect::DealClientConnect(Subject *s){
  40. ClientTCPConnect *tcp = (ClientTCPConnect *)s;
  41. tcp->GetData();//deal with TCP Data
  42. }

  应用:

  1. int n=10;
  2. ClientTCPConnect **ClientConnect = new ClientTCPConnect*[n];
  3. for(int i = ; i < n; i++){
  4. ClientConnect[i] = new ClientTCPConnect;
  5. }
  6. ServerTCPConnect *serverConnect = new ServerTCPConnect(ClientConnect, n);

  再复杂的就是多目标-多观察者模型,可由上多目标-单观察者和单目标-多观察者模型组合而成。

  1. int n = ;
  2. ClientTCPConnect **ClientConnect = new ClientTCPConnect*[n];
  3. for(int i = ; i < n; i++){
  4. ClientConnect[i] = new ClientTCPConnect;
  5. }
  6. int m = ;
  7. ServerTCPConnect **ServerConnect = new ServerTCPConnect*[m];
  8. for(int j = ; i < m; j++){
  9. ServerConnect[i] = new ServerTCPConnect(ClientConnect, n);
  10. }

   上面基本上都是相同的观察者和目标类,那么目标和观察者为不同的类时,该如何组合成多目标——多观察者的实例呢?

   目标类ClientTCPConnect,ClientTCPConnect1,、、、,ClientTCPConnect_n的实现:

  1. class ClientTCPConnect::public Subject{
  2. public:
  3. ClientTCPConnect();
  4.  
  5. virtual void OnReceivedData();//Received Data
  6.  
  7. virtual void GetData();
  8. };
  9.  
  10. void ClientTCPConnect::OnReceivedData(){
  11. //...
  12. Notify();
  13. }
  14.  
  15. void ClientTCPConnect::GetData(){
  16. //Get TCP Data
  17. }
  18. .
  19. .
  20. .
  21. class ClientTCPConnect_n::public Subject{
  22. public:
  23. ClientTCPConnect_n();
  24.  
  25. virtual void OnReceivedData_n();//Received Data
  26.  
  27. virtual void GetData_n();
  28. };
  29.  
  30. void ClientTCPConnect_n::OnReceivedData_n(){
  31. //...
  32. Notify();
  33. }
  34.  
  35. void ClientTCPConnect_n::GetData_n(){
  36. //Get TCP Data
  37. }

  观察者类ServerTCPConnect,ServerTCPConnect1,、、、,ServerTCPConnect_m的实现:

  1. class ServerTCPConnect::public Observer{
  2. public:
  3. ServerTCPConnect(Subject **, int size);
  4.  
  5. virtual ~ServerTCPConnect();
  6.  
  7. virtual void Update(Subject *);
  8. //overrides Observer operation
  9.  
  10. virtual void DealClientConnect(Subject *);
  11.  
  12. private:
  13. Subject **_subject;
  14. ClientTCPConnect_k *_subject_k;
  15. .
  16. .
  17. .
  18. ClientTCPConnect_l *_subject_l;
  19. int _size;
  20.  
  21. };
  22. ServerTCPConnect::ServerTCPConnect(Subject **s, int size){
  23. _subject = s;
  24. _subject_k = (ClientTCPConnect_k*)_subject[k];
  25. _subject_k->Attach(this);
  26. .
  27. .
  28. .
  29. _subject_l = (ClientTCPConnect_l*)_subject[l];
  30. _subject_l->Attach(this);
  31. }
  32.  
  33. ServerTCPConnect::~ServerTCPConnect(){
  34. _subject_k->Detach(this);
  35. .
  36. .
  37. .
  38. _subject_l->Detach(this);
  39. }
  40.  
  41. void ServerTCPConnect::Update(Subject * theChangedSubject){
  42. for(int i = ; i < _size; i++){
  43. if(theChangedSubject == _subject[i]){
  44. DealClientConnect(_subject[i]);
  45. }
  46. }
  47. }
  48.  
  49. void ServerTCPConnect::DealClientConnect(Subject *s){
  50. if(ClientTCPConnect *tcp = dynamic_cast<ClientTCPConnect*>(s)){
  51. tcp->GetData();//deal with TCP Data
  52. ...
  53. }
  54. .
  55. .
  56. .
  57. else if(ClientTCPConnect_n *tcp = dynamic_cast<ClientTCPConnect_n*>(s)){
  58. tcp->GetData_n();//deal with TCP Data
  59. ...
  60. }
  61. }
  62. .
  63. .
  64. .
  65. class ServerTCPConnect_m::public Observer{
  66. public:
  67. ServerTCPConnect_m(Subject **, int size);
  68.  
  69. virtual ~ServerTCPConnect_m();
  70.  
  71. virtual void Update(Subject *);
  72. //overrides Observer operation
  73.  
  74. virtual void DealClientConnect_m(Subject *);
  75.  
  76. private:
  77. Subject **_subject;
  78. ClientTCPConnect_k *_subject_k;
  79. .
  80. .
  81. .
  82. ClientTCPConnect_l *_subject_l;
  83. int _size;
  84. };
  85.  
  86. ServerTCPConnect_m::ServerTCPConnect_m(Subject **s, int size){
  87. _subject = s;
  88. _subject_k = (ClientTCPConnect_k*)_subject[k];
  89. _subject_k->Attach(this);
  90. .
  91. .
  92. .
  93. _subject_l = (ClientTCPConnect_l*)_subject[l];
  94. _subject_l->Attach(this);
  95. }
  96.  
  97. ServerTCPConnect_m::~ServerTCPConnect_m(){
  98. _subject_k->Detach(this);
  99. .
  100. .
  101. .
  102. _subject_l->Detach(this);
  103. }
  104.  
  105. void ServerTCPConnect_m::Update(Subject * theChangedSubject){
  106. for(int i = ; i < _size; i++){
  107. if(theChangedSubject == _subject[i]){
  108. DealClientConnect_m(_subject[i]);
  109. }
  110. }
  111. }
  112.  
  113. void ServerTCPConnect_m::DealClientConnect_m(Subject *s){
  114. if(ClientTCPConnect *tcp = dynamic_cast<ClientTCPConnect*>(s)){
  115. tcp->GetData();//deal with TCP Data
  116. ...
  117. }
  118. .
  119. .
  120. .
  121. else if(ClientTCPConnect_n *tcp = dynamic_cast<ClientTCPConnect_n*>(s)){
  122. tcp->GetData_n();//deal with TCP Data
  123. ...
  124. }
  125. }

  观察者模式模型图

  说了这么多,有时不如一张图来的直接,观察者模式可以用下UML图来表示,清晰明了!

                  观察者模式UML图b)

  著名的MVC(Model/View/Controller)模式也是基于OBSERVER设计模式的,Model类担任目标的角色,而View是观察者的基类,Controller用于控制应用程序的流程,它处理事件并作出响应。“事件”包括用户的行为和数据 Model 上的改变。

  MVC组件之间的典型合作c)

    观察者模式的优点是只要订阅/登记了之后,当目标改变时,观察者能自动更新。

参考资料:

a) 《设计模式:可复用面向对象软件的基础》

b)  https://en.wikipedia.org/wiki/Observer_pattern

c)  https://zh.wikipedia.org/wiki/MVC

面向对象设计模式——观察者(OBSERVER)模式的更多相关文章

  1. Java 实现观察者(Observer)模式

    1. Java自带的实现 类图 /** * 观察目标 继承自 java.util.Observable * @author stone * */ public class UpdateObservab ...

  2. 设计模式C++描述----04.观察者(Observer)模式

    一. 概述 Observer 模式要解决的问题为:建立一个一(Subject)对多(Observer)的依赖关系,并且做到当“一”变化的时候,依赖这个“一”的多也能够同步改变. Sbuject 相当于 ...

  3. Java设计模式之从[星际争霸的兵种升级]分析观察者(Observer)模式

    观察者模式定义对象的一种一对多的依赖关系.当一个对象的状态发生改变时.全部依赖于它的对象都会得到通知并被自己主动更新. 一个简单的样例是.在星际争霸的虫族中有一个0基础单位叫做跳狗(Zergling) ...

  4. 观察者(Observer)模式

    观察者模式又叫做发布-订阅模式(Publish.Subscribe)模式.模型-视图模式(Model/View)模式.源-监听器模式(Source/Listener)模式或从属者(Dependents ...

  5. 设计模式--观察者(Observer)

    GOF给出的定义: Define a one-to-many dependency between objects so that when one object changes state, all ...

  6. 设计模式之——Observer模式

    Observer模式又叫做观察者模式,当观察对象状态发生变化的时候,就会通知给观察者.这种模式适用于根据对象状态进行响应的场景! 实例程序是一个输出数字的程序. 观察者Observer类用于每500m ...

  7. 3)Javascript设计模式:Observer模式

    Observer模式 var Observer = (function() { var instance = null; function Observe() { this.events = {} } ...

  8. 面向对象设计模式_命令模式(Command)解读

    在.Net框架中很多对象的方法中都会有Invoke方法,这种方法的设计实际是用了设计模式的命令模式, 模式图如下 其核心思路是将Client 向Receiver发送的命令行为进行抽象(ICommand ...

  9. 设计模式之观察者(OBSERVER)模式

    定义 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新.  Observer模式描述了如何建立这种关系.这一模式中的关键对象是目标(subject ...

随机推荐

  1. 使用wamp扩展php时出现服务未启动的解决方法

    今天在使用wamp扩展php的插件时,出现了如下图所示的错误提示 网上查了查,都说是端口原因,修改Apache的 80端口,但是并没有解决问题. 最后我终于找到了解决方法,步骤很简单,如下: 首先,在 ...

  2. 互联网“剁手”新方向,VR全景购物忙——全景智慧城市常诚

    随着VR和AR技术的兴起,各行各业都在寻求VR+的对接方式,除了游戏和社交平台,另一大对VR有着浓厚兴趣的就是电商平台了,阿里.京东等电商巨头纷纷成立VR事业部,如何让亿万用户在VR中愉快的买买买,已 ...

  3. Java常用类之【日期相关类】

    一.日期类 Java语言提供了2个类来处理日期 Date类 Date类以毫秒来表示特定的日期 构造方法 Date date = new Date(); System.out.println(date) ...

  4. canvas实现视频截图

    截取视频当前播放画面,直接上源码. <body> <div class="container"> <video id="test" ...

  5. android 模拟器对应键盘快捷键

    一位同事的总结资料: Android SDK2.0.1自带的虚拟机尺寸都比较小(不针对机型,只为了在PC上看的舒服一点,当然越大越好了,我最大设置成1050*450,但是自带的屏保会不够宽,900*4 ...

  6. Spring学习(23)--- AOP之Introductions应用

    简介允许一个切面声明一个实现指定接口的通知对象,并且提供了一个接口实现类来代表这些对象 由<aop:aspect>中的<aop:declare-parents>元素声明该元素用 ...

  7. javascript的setTimeout()与setTimeout()方法用法总结

    setTimeout与setInterval的区别: setTimeout 定义和用法: setTimeout()方法用于在指定的毫秒数后调用函数或计算表达式. 语法: setTimeout(code ...

  8. js变量提升和函数提升

    变量,作为编程语言最基础的部分,每种语言的变量不尽相同,但又大径相庭.大部分编程语言的变量有块级作用域,如if.for.while... 但JavaScript不纯在块级作用域,而是函数作用域,并且有 ...

  9. 格式化输出和printf命令

    GNU版本的printf命令用来格式化输出,效果类似与C语言的printf函数.2.x以上版本的Bash内建的printf命令和e/usr/bin下的printf命令使用方法一样. 例子:$print ...

  10. find的用法

    find在Linux系统中和其它工具,如sed.awk.grep等结合起来用,非常有用. 1.列出系统中所有属于root用户的“set uid”文件 #find / -perm 4755 –uid 0 ...