C#设计模式(17)——观察者模式(Observer Pattern)

原文:C#设计模式(17)——观察者模式(Observer Pattern)

一、引言

  在现实生活中,处处可见观察者模式,例如,微信中的订阅号,订阅博客和QQ微博中关注好友,这些都属于观察者模式的应用。在这一章将分享我对观察者模式的理解,废话不多说了,直接进入今天的主题。

二、 观察者模式的介绍

2.1 观察者模式的定义

  从生活中的例子可以看出,只要对订阅号进行关注的客户端,如果订阅号有什么更新,就会直接推送给订阅了的用户。从中,我们就可以得出观察者模式的定义。

  观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己的行为。

2.2 观察者模式的结构

  从上面观察者模式的定义和生活中的例子,很容易知道,观察者模式中首先会存在两个对象,一个是观察者对象,另一个就是主题对象,然而,根据面向接口编程的原则,则自然就有抽象主题角色和抽象观察者角色。理清楚了观察者模式中涉及的角色后,接下来就要理清他们之间的关联了,要想主题对象状态发生改变时,能通知到所有观察者角色,则自然主题角色必须所有观察者的引用,这样才能在自己状态改变时,通知到所有观察者。有了上面的分析,下面观察者的结构图也就很容易理解了。具体结构图如下所示:

                                  图 观察者模式结构图

  可以看出,在观察者模式的结构图有以下角色:

  • 抽象主题角色(Subject):抽象主题把所有观察者对象的引用保存在一个列表中,并提供增加和删除观察者对象的操作,抽象主题角色又叫做抽象被观察者角色,一般由抽象类或接口实现。
  • 抽象观察者角色(Observer):为所有具体观察者定义一个接口,在得到主题通知时更新自己,一般由抽象类或接口实现。
  • 具体主题角色(ConcreteSubject):实现抽象主题接口,具体主题角色又叫做具体被观察者角色。
  • 具体观察者角色(ConcreteObserver):实现抽象观察者角色所要求的接口,以便使自身状态与主题的状态相协调。

2.3 观察者模式的实现

  下面以微信订阅号的例子来说明观察者模式的实现。现在要实现监控腾讯游戏订阅号的状态的变化。这里一开始不采用观察者模式来实现,而通过一步步重构的方式,最终重构为观察者模式。因为一开始拿到需求,自然想到有两个类,一个是腾讯游戏订阅号类,另一个是订阅者类。订阅号类中必须引用一个订阅者对象,这样才能在订阅号状态改变时,调用这个订阅者对象的方法来通知到订阅者对象。有了这个分析,自然实现的代码如下所示:

 1  // 腾讯游戏订阅号类
 2     public class TenxunGame
 3     {
 4         // 订阅者对象
 5         public Subscriber Subscriber {get;set;}
 6
 7         public String Symbol {get; set;}
 8
 9         public string Info {get ;set;}
10
11         public void Update()
12         {
13             if (Subscriber != null)
14             {
15                 // 调用订阅者对象来通知订阅者
16                 Subscriber.ReceiveAndPrintData(this);
17             }
18         }
19
20     }
21
22     // 订阅者类
23     public class Subscriber
24     {
25         public string Name { get; set; }
26         public Subscriber(string name)
27         {
28             this.Name = name;
29         }
30
31         public void ReceiveAndPrintData(TenxunGame txGame)
32         {
33             Console.WriteLine("Notified {0} of {1}‘s" + " Info is: {2}", Name, txGame.Symbol, txGame.Info);
34         }
35     }
36
37     // 客户端测试
38     class Program
39     {
40         static void Main(string[] args)
41         {
42             // 实例化订阅者和订阅号对象
43             Subscriber LearningHardSub = new Subscriber("LearningHard");
44             TenxunGame txGame = new TenxunGame();
45
46             txGame.Subscriber = LearningHardSub;
47             txGame.Symbol = "TenXun Game";
48             txGame.Info = "Have a new game published ....";
49
50             txGame.Update();
51
52             Console.ReadLine();
53         }
54     }

  上面代码确实实现了监控订阅号的任务。但这里的实现存在下面几个问题:

  • TenxunGame类和Subscriber类之间形成了一种双向依赖关系,即TenxunGame调用了Subscriber的ReceiveAndPrintData方法,而Subscriber调用了TenxunGame类的属性。这样的实现,如果有其中一个类变化将引起另一个类的改变。
  • 当出现一个新的订阅者时,此时不得不修改TenxunGame代码,即添加另一个订阅者的引用和在Update方法中调用另一个订阅者的方法。

  上面的设计违背了“开放——封闭”原则,显然,这不是我们想要的。对此我们要做进一步的抽象,既然这里变化的部分是新订阅者的出现,这样我们可以对订阅者抽象出一个接口,用它来取消TenxunGame类与具体的订阅者之间的依赖,做这样一步改进,确实可以解决TenxunGame类与具体订阅者之间的依赖,使其依赖与接口,从而形成弱引用关系,但还是不能解决出现一个订阅者不得不修改TenxunGame代码的问题。对此,我们可以做这样的思考——订阅号存在多个订阅者,我们可以采用一个列表来保存所有的订阅者对象,在订阅号内部再添加对该列表的操作,这样不就解决了出现新订阅者的问题了嘛。并且订阅号也属于变化的部分,所以,我们可以采用相同的方式对订阅号进行抽象,抽象出一个抽象的订阅号类,这样也就可以完美解决上面代码存在的问题了,具体的实现代码为:

 1 // 订阅号抽象类
 2     public abstract class TenXun
 3     {
 4         // 保存订阅者列表
 5         private List<IObserver> observers = new List<IObserver>();
 6
 7         public string Symbol { get; set; }
 8         public string Info { get; set; }
 9         public TenXun(string symbol, string info)
10         {
11             this.Symbol = symbol;
12             this.Info = info;
13         }
14
15         #region 新增对订阅号列表的维护操作
16         public void AddObserver(IObserver ob)
17         {
18             observers.Add(ob);
19         }
20         public void RemoveObserver(IObserver ob)
21         {
22             observers.Remove(ob);
23         }
24         #endregion
25
26         public void Update()
27         {
28             // 遍历订阅者列表进行通知
29             foreach (IObserver ob in observers)
30             {
31                 if (ob != null)
32                 {
33                     ob.ReceiveAndPrint(this);
34                 }
35             }
36         }
37     }
38
39     // 具体订阅号类
40     public class TenXunGame : TenXun
41     {
42         public TenXunGame(string symbol, string info)
43             : base(symbol, info)
44         {
45         }
46     }
47
48     // 订阅者接口
49     public interface IObserver
50     {
51         void ReceiveAndPrint(TenXun tenxun);
52     }
53
54     // 具体的订阅者类
55     public class Subscriber : IObserver
56     {
57         public string Name { get; set; }
58         public Subscriber(string name)
59         {
60             this.Name = name;
61         }
62
63         public void ReceiveAndPrint(TenXun tenxun)
64         {
65             Console.WriteLine("Notified {0} of {1}‘s" + " Info is: {2}", Name, tenxun.Symbol, tenxun.Info);
66         }
67     }
68
69     // 客户端测试
70     class Program
71     {
72         static void Main(string[] args)
73         {
74             TenXun tenXun = new TenXunGame("TenXun Game", "Have a new game published ....");
75
76             // 添加订阅者
77             tenXun.AddObserver(new Subscriber("Learning Hard"));
78             tenXun.AddObserver(new Subscriber("Tom"));
79
80             tenXun.Update();
81
82             Console.ReadLine();
83         }
84     }

  上面代码是我们进行重构后的实现,重构后的代码实现类图如下所示:

  从上图可以发现,这样的实现就是观察者模式的实现。这样,在任何时候,只要调用了TenXun类的Update方法,它就会通知所有的观察者对象,同时,可以看到,观察者模式,取消了直接依赖,变为间接依赖,这样大大提供了系统的可维护性和可扩展性。这里并不是直接给出观察者模式的实现,而是通过一步步重构的方式来引出观察者模式的实现,相信通过这个方式,大家可以更深刻地理解观察者模式所解决的问题和带来的好处。

三、.NET 中观察者模式的应用

  在.NET中,我们可以使用委托与事件来简化观察者模式的实现,上面的例子用事件和委托的实现如下代码所示:

 1 namespace ObserverInNET
 2 {
 3     class Program
 4     {
 5         // 委托充当订阅者接口类
 6         public delegate void NotifyEventHandler(object sender);
 7
 8         // 抽象订阅号类
 9         public class TenXun
10         {
11             public NotifyEventHandler NotifyEvent;
12
13             public string Symbol { get; set; }
14             public string Info { get; set; }
15             public TenXun(string symbol, string info)
16             {
17                 this.Symbol = symbol;
18                 this.Info = info;
19             }
20
21             #region 新增对订阅号列表的维护操作
22             public void AddObserver(NotifyEventHandler ob)
23             {
24                 NotifyEvent += ob;
25             }
26             public void RemoveObserver(NotifyEventHandler ob)
27             {
28                 NotifyEvent -= ob;
29             }
30
31             #endregion
32
33             public void Update()
34             {
35                 if (NotifyEvent != null)
36                 {
37                     NotifyEvent(this);
38                 }
39             }
40         }
41
42         // 具体订阅号类
43         public class TenXunGame : TenXun
44         {
45             public TenXunGame(string symbol, string info)
46                 : base(symbol, info)
47             {
48             }
49         }
50
51         // 具体订阅者类
52         public class Subscriber
53         {
54             public string Name { get; set; }
55             public Subscriber(string name)
56             {
57                 this.Name = name;
58             }
59
60             public void ReceiveAndPrint(Object obj)
61             {
62                 TenXun tenxun = obj as TenXun;
63
64                 if (tenxun != null)
65                 {
66                     Console.WriteLine("Notified {0} of {1}‘s" + " Info is: {2}", Name, tenxun.Symbol, tenxun.Info);
67                 }
68             }
69         }
70
71         static void Main(string[] args)
72         {
73             TenXun tenXun = new TenXunGame("TenXun Game", "Have a new game published ....");
74             Subscriber lh =  new Subscriber("Learning Hard");
75             Subscriber tom =  new Subscriber("Tom");
76
77             // 添加订阅者
78             tenXun.AddObserver(new NotifyEventHandler(lh.ReceiveAndPrint));
79             tenXun.AddObserver(new NotifyEventHandler(tom.ReceiveAndPrint));
80
81             tenXun.Update();
82
83             Console.WriteLine("-----------------------------------");
84             Console.WriteLine("移除Tom订阅者");
85             tenXun.RemoveObserver(new NotifyEventHandler(tom.ReceiveAndPrint));
86             tenXun.Update();
87
88             Console.ReadLine();
89         }
90     }
91 }

  从上面代码可以看出,使用事件和委托实现的观察者模式中,减少了订阅者接口类的定义,此时,.NET中的委托正式充到订阅者接口类的角色。使用委托和事件,确实简化了观察者模式的实现,减少了一个IObserver接口的定义,上面代码的运行结果如下图所示:

四、观察者模式的适用场景

  在下面的情况下可以考虑使用观察者模式:

  • 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两者封装在独立的对象中以使它们可以各自独立地改变和复用的情况下。从方面的这个词中可以想到,观察者模式肯定在AOP(面向方面编程)中有所体现,更多内容参考:Observern Pattern in AOP.
  • 当对一个对象的改变需要同时改变其他对象,而又不知道具体有多少对象有待改变的情况下。
  • 当一个对象必须通知其他对象,而又不能假定其他对象是谁的情况下。

五、观察者模式的优缺点

  观察者模式有以下几个优点:

  • 观察者模式实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层,即观察者。
  • 观察者模式在被观察者和观察者之间建立了一个抽象的耦合,被观察者并不知道任何一个具体的观察者,只是保存着抽象观察者的列表,每个具体观察者都符合一个抽象观察者的接口。
  • 观察者模式支持广播通信。被观察者会向所有的注册过的观察者发出通知。

  观察者也存在以下一些缺点:

  • 如果一个被观察者有很多直接和间接的观察者时,将所有的观察者都通知到会花费很多时间。
  • 虽然观察者模式可以随时使观察者知道所观察的对象发送了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎样发生变化的。
  • 如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃,在使用观察者模式应特别注意这点。

六 总结

  到这里,观察者模式的分享就介绍了。观察者模式定义了一种一对多的依赖关系,让多个观察者对象可以同时监听某一个主题对象,这个主题对象在发生状态变化时,会通知所有观察者对象,使它们能够自动更新自己,解决的是“当一个对象的改变需要同时改变多个其他对象”的问题。大家可以以微信订阅号的例子来理解观察者模式。

时间: 2024-11-08 21:22:29

C#设计模式(17)——观察者模式(Observer Pattern)的相关文章

Java设计模式之观察者模式(Observer Pattern)

Observer Pattern 是一种常用的设计模式,它是一种事件监听模型.该模式有两个角色,一个是Subject, 另一个是Observer.Subject 保存有多个Observer的引用,一旦特定的事件发生,Subject会通知它所有的Observer,Observer得到该通知后执行相关程序逻辑.其中,Observer只有先向Subject注册后才能被Subject知晓.这就像订报纸,只有我们向出版社提出订报的申请,出版社才会把我们列入订阅者名单,然后每当新报纸印好时,出版社会通知订阅

【设计模式】观察者模式 Observer Pattern

定义:观察者模式定义了对象之间的一对多依赖.当“主题”(Object)状态改变事,所有依赖它的“观察者”(Observer)都会受到通知并自动更新.主题支持观察者订阅和退订. 观察者模式提供了一种对象设计,让主题和观察者之间松耦合.改变主题或观察者一方不会影响另一方.因为两者是松耦合的. 参考: 设计模式学习笔记--Observer Pattern观察者模式

17.观察者模式(Observer Pattern)

using System; using System.Collections.Generic; namespace ConsoleApplication10 { /// <summary> /// 在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系” /// ——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知. /// 如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化.使用面向对象技术,可以将这种依赖关系弱化, /// 并形成一种稳定的依赖关系.从而

设计模式 - 观察者模式(Observer Pattern) 详解

观察者模式(Observer Pattern) 详解 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26583157 版权所有, 禁止转载, 如有转载, 请站内联系. 观察者模式(Observer Pattern): 定义了对象之间的一对多的依赖, 这样一来, 当一个对象改变状态时, 它的所有依赖者都会收到通知并自动更新. 使用方法: 1. 首先新建主题(subject)接口, 负责注册(register)\删除(remove

设计模式 - 观察者模式(Observer Pattern) Java内置 使用方法

观察者模式(Observer Pattern) Java内置 使用方法 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26601659 观察者模式(observer pattern)详解, 参见: http://blog.csdn.net/caroline_wendy/article/details/26583157 Java内置的观察者模式, 是通过继承父类, 实现观察者模式的几个主要函数: Observerable(可被观

设计模式 - 观察者模式(Observer Pattern) 详细说明

观察者模式(Observer Pattern) 详细说明 本文地址: http://blog.csdn.net/caroline_wendy/article/details/26583157 版权全部, 禁止转载, 如有转载, 请站内联系. 观察者模式(Observer Pattern): 定义了对象之间的一对多的依赖, 这样一来, 当一个对象改变状态时, 它的全部依赖者都会收到通知并自己主动更新. 用法: 1. 首先新建主题(subject)接口, 负责注冊(register)\删除(remo

设计模式之观察者模式(Observer)摘录

23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象.创建型模式有两个不断出现的主旋律.第一,它们都将关于该系统使用哪些具体的类的信息封装起来.第二,它们隐藏了这些类的实例是如何被创建和放在一起的.整个系统关于这些对象所知道的是由抽象类所定义的接口.因此,创建型模式在什么被创建,谁创建它,它是怎样被创建的,以

jQuery中的观察者模式(Observer Pattern)

在jQuery中,on方法可以为元素绑定事件,trigger方法可以手动触发事件,围绕这2个方法,我们来体验jQuery中的观察者模式(Observer Pattern). ■ on方法绑定内置事件,自然触发 比如,我们给页面的body元素绑定一个click事件,这样写. <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title&

设计模式:观察者模式--Observer

一.什么是观察者模式 1.生活中的观察者模式 1.警察抓小偷 在现实生活中,警察抓小偷是一个典型的观察者模式「这以一个惯犯在街道逛街然后被抓为例子」,这里小偷就是被观察者,各个干警就是观察者,干警时时观察着小偷,当小偷正在偷东西「就给干警发送出一条信号,实际上小偷不可能告诉干警我有偷东西」,干警收到信号,出击抓小偷.这就是一个观察者模式 2.装模作样写作业 小时候家里家活比较多,爸妈让我去干活的时候,我偶尔会说我要学习「其实不想去干活,当然只是偶尔,我还是常常干家务的」,然后爸妈就去地里了,我一