34观察者模式(Observer Pattern)

动机(Motivate):
    在软件构建 过程中,我们需要为某些对象建立一种“通知依赖关系” --------一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。使用面 向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
意图(Intent):
    
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。
                                                                         -------《设计模式》GOF
结构图(Struct):
             
适用性:

1.当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。

2.当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。

3.当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
生活中的例子:  

    观 察者定义了对象间一对多的关系,当一个对象的状态变化时,所有依赖它的对象都得到通知并且自动地更新。在ATM取款,当取款成功后,以手机、邮件等方式进行通知。

 

                             
代码实现:
 1     public class BankAccount
 2     {
 3         Emailer emailer;  //强信赖关系
 4         Mobile  phoneNumber;    //强信赖关系
 5        
 6         private double _money;
 7 
 8         public Emailer Emailer
 9         {
10             get { return emailer; }
11             set { this.emailer = value; }
12         }
13         public Mobile PhoneNumber
14         {
15             get { return phoneNumber; }
16             set { this.phoneNumber = value; }
17         }
18         public double Money
19         {
20             get { return _money; }
21             set { this._money = value; }
22         }
23   
24         public void WithDraw()
25         {
26             emailer.SendEmail(this);
27             phoneNumber.SendNotification(this);
28         }
29 
30     }
 
 1     public class Emailer
 2     {
 3        private string _emailer;
 4        public Emailer(string emailer)
 5        {
 6            this._emailer = emailer;
 7        }
 8         public void SendEmail(BankAccount ba)
 9         {
10             //..
11             Console.WriteLine("Notified : Emailer is {0}, You withdraw  {1:C} ", _emailer, ba.Money);
12         }
13     }
 
 1     public class Mobile
 2     {
 3         private long _phoneNumber;
 4         public Mobile(long phoneNumber)
 5         {
 6             this._phoneNumber = phoneNumber;
 7         }
 8         public void SendNotification(BankAccount ba)
 9         {
10             Console.WriteLine("Notified :Phone number is {0} You withdraw  {1:C} ", _phoneNumber, ba.Money);
11         }
12     }
此时简单的客户端调用如下:
 
 1 class Test
 2 {
 3     static void Main(string[] args)
 4     {
 5         BankAccount ba = new BankAccount();
 6         Emailer emailer = new Emailer("abcdwxc@163.com");       
 7         Mobile mobile = new Mobile(13901234567);
 8         ba.Emailer = emailer;
 9         ba.PhoneNumber = mobile;
10         ba.Money = 2000;       
11         ba.WithDraw();
12     }
13 }
运行结果如下:

    由此可见程序可以正常运行,但请注意BandAccount和Emailer及Mobile之间形成了一种双向的依赖关系,即BankAccount调用了Emailer及Mobile的方法,而Emailer及Mobile调用了BnadAccount类的属性。如果有其中一个类变化,有可能会引起另一个的变化。如果又需添加一种新的通知方式,就得在BankAccount的WithDraw()方法中增加对该中通知方式的调用。
    显然这样的设计极大的违背了“开放-封闭”原则,这不是我们所想要的,仅仅是新增加了一种通知对象,就需要对原有的BankAccount类进行修改,这样的设计是很糟糕的。对此做进一步的抽象,既然出现了多个通知对象,我们就为这些对象之间抽象出一个接口,用它来取消BankAccount和具体的通知对象之间依赖。
由此我们由左图转换到右图。
             
实例代码如下:
1   public interface IObserverAccount
2         {
3             void Update(BankAccount ba);
4         }
 
 1  public class BankAccount
 2         {
 3             IObserverAccount emailer;        //依赖于接口
 4             IObserverAccount phoneNumber;    //依赖于接口
 5 
 6             private double _money;
 7 
 8             public IObserverAccount Emailer
 9             {
10                 get { return emailer; }
11                 set { this.emailer = value; }
12             }
13             public IObserverAccount PhoneNumber
14             {
15                 get { return phoneNumber; }
16                 set { this.phoneNumber = value; }
17             }
18             public double Money
19             {
20                 get { return _money; }
21                 set { this._money = value; }
22             }
23 
24             public void WithDraw()
25             {
26                 emailer.Update(this);
27                 phoneNumber.Update(this);
28             }
29 
30         }
 
 1       public class Emailer : IObserverAccount
 2         {
 3             private string _emailer;
 4             public Emailer(string emailer)
 5             {
 6                 this._emailer = emailer;
 7             }
 8             public void Update(BankAccount ba)
 9             {
10                 //..
11                 Console.WriteLine("Notified : Emailer is {0}, You withdraw  {1:C} ", _emailer, ba.Money);
12             }
13         }
 
 1       public class Mobile : IObserverAccount
 2         {
 3             private long _phoneNumber;
 4             public Mobile(long phoneNumber)
 5             {
 6                 this._phoneNumber = phoneNumber;
 7             }
 8             public void Update(BankAccount ba)
 9             {
10                 Console.WriteLine("Notified :Phone number is {0} You withdraw  {1:C} ", _phoneNumber, ba.Money);
11             }
12         }
客户端与上方相同,其运行结果也相同。但BankAccount增加和删除通知对象时,还需对其进行修改。对此我们再做如下重构,在BankAccount中维护一个IObserver列表,同时提供相应的维护方法。
 1     public class BankAccount
 2     {
 3         private List<IObserverAccount> Observers = new List<IObserverAccount>();
 4 
 5 
 6         private double _money;
 7 
 8         public double Money
 9         {
10             get { return _money; }
11             set { this._money = value; }
12         }
13 
14         public void WithDraw()
15         {
16             foreach (IObserverAccount ob in Observers)
17             {
18                 ob.Update(this);
19 
20             }
21         }
22         public void AddObserver(IObserverAccount observer)
23         {
24             Observers.Add(observer);
25         }
26         public void RemoverObserver(IObserverAccount observer)
27         {
28             Observers.Remove(observer);
29         }
30 
31     }
此时客户端代码如下:
 1   class Test
 2     {
 3         static void Main(string[] args)
 4         {
 5             BankAccount ba = new BankAccount();
 6             IObserverAccount emailer = new Emailer("abcdwxc@163.com");
 7             IObserverAccount mobile = new Mobile(13901234567);
 8 
 9             ba.Money = 2000;
10             ba.AddObserver(emailer);
11             ba.AddObserver(mobile);
12 
13             ba.WithDraw();
14         }
15     }
    走到这一步,已经有了Observer模式的影子了,BankAccount类不再依赖于具体的Emailer或Mobile,而是依赖于抽象的IObserverAccount。存在着的一个问题是Emailer或Mobile仍然依赖于具体的BankAccount,解决这样的问题很简单,只需要再对BankAccount类做一次抽象。如下图:
          
 1  public abstract class Subject
 2     {
 3         private List<IObserverAccount> Observers = new List<IObserverAccount>();
 4 
 5         private double _money;
 6         public Subject(double money)
 7         {
 8             this._money = money;
 9         }
10 
11         public double Money
12         {
13             get { return _money; }
14         }
15      
16         public void WithDraw()
17         {
18             foreach (IObserverAccount ob in Observers)
19             {
20                 ob.Update(this);
21 
22             }
23         }
24         public void AddObserver(IObserverAccount observer)
25         {
26             Observers.Add(observer);
27         }
28         public void RemoverObserver(IObserverAccount observer)
29         {
30             Observers.Remove(observer);
31         }
32 
33     }
 
1     public interface IObserverAccount
2     {
3         void Update(Subject subject);
4     }
 
1     public class BankAccount : Subject
2     {
3         public BankAccount(double money)
4             : base(money)
5         { }
6 
7     }
 
 1     public class Emailer : IObserverAccount
 2     {
 3         private string _emalier;      
 4         public Emailer(string emailer )
 5         {
 6             this._emalier = emailer;           
 7         }
 8         public void Update(Subject subject)
 9         {            
10             Console.WriteLine("Notified : Emailer is {0}, You withdraw  {1:C} ", _emalier, subject.Money);
11         }
12     }
 
 1    public class Mobile : IObserverAccount
 2     {
 3         private long _phoneNumber;        
 4         public Mobile(long phoneNumber)
 5         {
 6             this._phoneNumber = phoneNumber;            
 7         }
 8         public void Update(Subject subject)
 9         {
10             Console.WriteLine("Notified :Phone number is {0} You withdraw  {1:C} ", _phoneNumber, subject.Money);
11         }
12     }
此时客户端实现如下:
 1    class Test
 2     {
 3         static void Main(string[] args)
 4         {
 5             Subject subject = new BankAccount(2000);
 6             subject.AddObserver(new Emailer("abcdwxc@163.com"));
 7             subject.AddObserver(new Mobile(13901234567));
 8 
 9             subject.WithDraw();
10         }
11     }

推模式与拉模式
    对于发布-订阅模型,大家都很容易能想到推模式与拉模式,用SQL Server做过数据库复制的朋友对这一点很清楚。在Observer模式中同样区分推模式和拉模式,我先简单的解释一下两者的区别:推模式是当有消息时,把消息信息以参数的形式传递(推)给所有观察者,而拉模式是当有消息时,通知消息的方法本身并不带任何的参数,是由观察者自己到主体对象那儿取回(拉)消息。知道了这一点,大家可能很容易发现上面我所举的例子其实是一种推模式的Observer模式。我们先看看这种模式带来了什么好处:当有消息时,所有的 观察者都会直接得到全部的消息,并进行相应的处理程序,与主体对象没什么关系,两者之间的关系是一种松散耦合。但是它也有缺陷,第一是所有的观察者得到的 消息是一样的,也许有些信息对某个观察者来说根本就用不上,也就是观察者不能“按需所取”;第二,当通知消息的参数有变化时,所有的观察者对象都要变化。鉴于以上问题,拉模式就应运而生了,它是由观察者自己主动去取消息,需要什么信息,就可以取什么,不会像推模式那样得到所有的消息参数。
拉模式实现如下:
 1 public abstract class Subject
 2     {
 3         private List<IObserverAccount> Observers = new List<IObserverAccount>();
 4 
 5 
 6         private double _money;
 7 
 8         public double Money
 9         {
10             get { return _money; }            
11         }
12         public Subject(double money)
13         {
14             this._money = money;
15         }
16         public void WithDraw()
17         {
18             foreach (IObserverAccount ob in Observers)
19             {
20                 ob.Update();
21 
22             }
23         }
24         public void AddObserver(IObserverAccount observer)
25         {
26             Observers.Add(observer);
27         }
28         public void RemoverObserver(IObserverAccount observer)
29         {
30             Observers.Remove(observer);
31         }
32 
33     }
 
1    public interface IObserverAccount
2     {
3         void Update();
4     }
 
1     public class BankAccount :Subject
2     {
3         public BankAccount(double money)
4             : base(money)
5         { }
6        
7     }
 
 1     public class Emailer : IObserverAccount
 2     {
 3         private string _emalier;
 4         private Subject _subject;
 5         public Emailer(string emailer,Subject subject)
 6         {
 7             this._emalier = emailer;
 8             this._subject = subject;
 9         }
10         public void Update()
11         {
12             //..
13             Console.WriteLine("Notified : Emailer is {0}, You withdraw  {1:C} ", _emalier,_subject.Money);
14         }
15     }
 
 1     public class Mobile : IObserverAccount
 2     {
 3         private long _phoneNumber;
 4         private Subject _subject;
 5         public Mobile(long phoneNumber,Subject subject)
 6         {
 7             this._phoneNumber = phoneNumber;
 8             this._subject = subject;
 9         }
10         public void Update()
11         {
12             Console.WriteLine("Notified :Phone number is {0} You withdraw  {1:C} ", _phoneNumber,_subject.Money);
13         }
14     }
此时客户端调用如下:
 1    class Test
 2     {
 3         static void Main(string[] args)
 4         {
 5            Subject subject= new BankAccount(2000);          
 6             subject.AddObserver(new Emailer("abcdwxc@163.com",subject));
 7             subject.AddObserver(new Mobile(13901234567,subject));
 8 
 9             subject.WithDraw();
10         }
11     }
.NET中Observer实现:
    
用事件和委托来实现Observer模式我认为更加的简单和优雅,也是一种更好的解决方案。
 1  public  class Subject
 2     {
 3         public event NotifyEventHandler NotifyEvent;
 4 
 5         private double _money;
 6         public Subject(double money)
 7         {
 8             this._money = money;
 9         }
10 
11         public double Money
12         {
13             get { return _money; }
14         }
15 
16         public void WithDraw()
17         {
18             OnNotifyChange();
19         }
20         public void OnNotifyChange()
21         {
22             if (NotifyEvent != null)
23             {
24                 NotifyEvent(this);
25             }
26 
27         }
28 
29     }
 
 1     public class Emailer
 2     {
 3         private string _emalier;
 4         public Emailer(string emailer)
 5         {
 6             this._emalier = emailer;
 7         }
 8         public void Update(object obj)
 9         {
10             if (obj is Subject)
11             {
12                 Subject subject = (Subject)obj;
13 
14                 Console.WriteLine("Notified : Emailer is {0}, You withdraw  {1:C} ", _emalier, subject.Money);
15             }
16         }
17 }
public delegate void NotifyEventHandler(object sender);
客户端调用如下:
 1     class Test
 2         {
 3             static void Main(string[] args)
 4             {
 5                 Subject subject = new Subject(2000);
 6                 Emailer emailer = new Emailer("abcdwxc@163.com");
 7                 subject.NotifyEvent += new NotifyEventHandler(emailer.Update);
 8            
 9 
10                 subject.WithDraw();
11             }
12         }

Observer实现要点:

1.使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达到松耦合。

2.目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。观察者自己决定是否需要订阅通知。目标对象对此一无所知。

3.在C#中的Event。委托充当了抽象的Observer接口,而提供事件的对象充当了目标对象,委托是比抽象Observer接口更为松耦合的设计。
#9楼 2007-12-25 16:08 wycg_cnh20
拉模式和推模式的差别在概念上讲得很清楚,但是,从代码上没看出推和拉的差别来/
支持(0) 反对(0)

  

  
 
#12楼 2009-04-17 14:30 soxyunyi
是否体现推拉,是由观察者类的update方法参数决定的。
支持(0) 反对(0)

  
 
#13楼 2009-04-17 14:34 soxunyi
public void update(Observable obs, Object arg) 
这是Java中Obersver接口方法, 
如果在方法体中通过obs来获取主题的状态变化,则是拉模式; 
如果在方法体中通过arg来获取主题的状态变化,则是推模式。 
因为,通过obs获取是在观察者类中实现的,故为拉。 
而arg则是由主题类传递给观察者的,故为推。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/438635.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

36中介者模式(Mediator Pattern)

依赖关系的转化: 动机(Motivate): 在软件构建过程中&#xff0c;经常会出现多个对象互相关联交互的情况&#xff0c;对象之间常常会维持一种复杂的引用关系&#xff0c;如果遇到一些需求的更改&#xff0c;这种直接的引用关系将面临不断的变化。 在这种情况…

相似理论与模型试验_正交实验下的固液耦合相似材料研究

原标题&#xff1a;基于正交试验的固液耦合相似材料研究摘 要:为了研究矿井突水演化规律&#xff0c;通过正交试验研制出一种能同时满足固体力学与水理性的固液 耦合相似材料&#xff0c;该相似材料以河沙为骨料、水泥和大白粉为胶结剂、液体石蜡和淀粉为调节剂。采用 极差分析…

35解释器模式(Interpreter Pattern)

动机(Motivate): 在软件构建过程中&#xff0c;如果某一特定领域的问题比较复杂&#xff0c;类似的模式不断重复出现&#xff0c;如果使用普通的编程方式来实现将面临非常频繁的变化。 在这种情况下&#xff0c;将特定领域的问题表达为某种文法规则下的句子&#xff0c;…

37职责链模式(Chain of Responsibility Pattern)

动机(Motivate)&#xff1a; 在软件构建过程中&#xff0c;一个请求可能被多个对象处理&#xff0c;但是每个请求在运行时只能有一个接受者&#xff0c;如果显示指定&#xff0c;将必不可少地带来请求发送者与接受者的紧耦合。 如何使请求的发送者不需要指定具体的接受…

python3中format函数列表_Python3之字符串格式化format函数详解(上)

173.jpg概述在Python3中&#xff0c;字符串格式化操作通过format()方法或者fstring实现。而相比于老版的字符串格式化方式&#xff0c;format()方法拥有更多的功能&#xff0c;操作起来更加方便&#xff0c;可读性也更强。该函数将字符串当成一个模板&#xff0c;通过传入的参数…

38备忘录模式(Memento Pattern)

对象状态的回溯&#xff1a; 对象状态的变化无端&#xff0c;如何回溯/恢复对象在某个点的状态&#xff1f; 动机&#xff1a; 在软件构建过程中&#xff0c;某些对象的状态在转换过程中&#xff0c;可能由于某种需要&#xff0c;要求程序能够…

39策略模式(Strategy Pattern)

算法与对象的耦合&#xff1a; 对象可能经常需要使用多种不同的算法&#xff0c;但是如果变化频繁&#xff0c;会将类型变得脆弱... 动机&#xff1a; 在软件构建过程中&#xff0c;某些对象使用的算法可能多种多样&#xff0c;经常改变&#xff0c;如果将…

40访问者模式(Visitor Pattern)

类层次结构的变化&#xff1a; 类层次结构中可能经常由于引入新的操作&#xff0c;从而将类型变得脆弱... 动机&#xff1a; 在软件构建过程中&#xff0c;由于需求的改变&#xff0c;某些类层次结构中常常需要增加新的行为(方法),如果直接…

41状态模式(State Pattern)

对象状态影响对象行为&#xff1a; 对象拥有不同的状态&#xff0c;往往会行使不同的行为... 动机&#xff1a; 在软件构建过程中&#xff0c;某些对象的状态如果改变以及其行为也会随之而发生变化&#xff0c;比如文档处于只读状态&#xff0c;其支…

python中空格属于字符吗_举例说明python中空格是属于字符

python中空格属于字符吗&#xff1f;答案是肯定的&#xff0c;空格在Python中也是属于字符的。案例&#xff1a;输入一行字符&#xff0c;分别统计出其中英文字母、空格、数字和其它字符的个数。#!/usr/bin/python# -*- coding: UTF-8 -*-import strings raw_input(input a st…

【转】如何将域中的AD数据导入SharePoint

最近刚装好sharepoint2010&#xff0c;想要研究一下&#xff0c;第一件想做的事就是想把AD中的用户信息导入到SharePoint中。 那现在就来看看我是怎么操作的&#xff1a; 1.打开管理中心 sharepoint是通过“用户配置文件同步服务”来实现同步&#xff0c;所以第一步要开启这个…

Apsara Clouder专项技能认证:实现调用API接口

一.API 简介 1.API 的概念 API(Application Programming Interface应用程序编程接口)是一些预定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码或理解内部工作机制的细节 2.API 的特点 API 是一个明确定义的接口,可以为其…

第一节:复习委托,并且通过委托的异步调用开启一个新线程和异步回调、异步等待

一. 再谈委托 1. 委托是一个关键字为delegate的自定义类型&#xff0c;通过委托可以把方法以参数的形式传递给另外一个方法&#xff0c;实现插件式的开发模式&#xff1b; 同时调用委托的时候&#xff0c;委托所包含的所有方法都会被实现。 2. 委托的发展历史&#xff1a;new…

linux 修改 java 内存_Linux 和 Windows修改Java虚拟机内存大小

因为内存溢出问题1. Linux下直接修改%tomcat_home%/bin/catalina.sh文件在注释下紧接一行也就是脚本正文开始之前 加上Java_OPTS-server -Xms512m -Xmx1024m -XX:PermSize128m -XX:MaxPermSize512m如果报-x没有定义,则用declare -x JAVA_OPTS"-Xms512m -Xmx1024"初始…

第二节:深入剖析Thread的五大方法、数据槽、内存栅栏

一. Thread及其五大方法 Thread是.Net最早的多线程处理方式&#xff0c;它出现在.Net1.0时代&#xff0c;虽然现在已逐渐被微软所抛弃&#xff0c;微软强烈推荐使用Task(后面章节介绍)&#xff0c;但从多线程完整性的角度上来说&#xff0c;我们有必要了解下N年前多线程的是怎么…

java redis 生成唯一id_Redis在集群环境中生成唯一ID

概述设计目标&#xff1a;每秒最大生成10万个ID&#xff0c;ID单调递增且唯一。Reidis可以不需要持久化ID。要求:集群时钟不能倒退。总体思路&#xff1a;集群中每个节点预生成生成ID&#xff1b;然后与redis的已经存在的ID做比较。如果大于&#xff0c;则取节点生成的ID&#…

java await signal_【Java并发008】原理层面:ReentrantLock中 await()、signal()/signalAll()全解析...

一、前言上篇的文章中我们介绍了AQS源码中lock方法和unlock方法&#xff0c;这两个方法主要是用来解决并发中互斥的问题&#xff0c;这篇文章我们主要介绍AQS中用来解决线程同步问题的await方法、signal方法和signalAll方法&#xff0c;这几个方法主要对应的是synchronized中的…

第八节:Task的各类TaskTResult返回值以及通用线程的异常处理方案

一. Task的各种返回值-Task<TResult> PS&#xff1a; 在前面章节&#xff0c;我们介绍了Task类开启线程、线程等待、线程延续的方式&#xff0c;但我们并没有关注这些方式的返回值&#xff0c;其实他们都是有返回值的Task<TResult>&#xff0c;然后可以通过Task的…

mysql2005触发器修改成绩_创建、更改和删除触发器

创建、更改和删除触发器Creating, Altering, and Removing Triggers08/06/2017本文内容适用于&#xff1a;Applies to: SQL ServerSQL Server(所有支持的版本)SQL ServerSQL Server (all supported versions) Azure SQL 数据库Azure SQL DatabaseAzure SQL 数据库Azure SQL Dat…

第一节:从面向对象思想(oo)开发、接口、抽象类以及二者比较

一. 面向对象思想 1. 面向过程&#xff08;OP&#xff09;和面向对象&#xff08;OO&#xff09;的区别&#xff1a; (1)&#xff1a;面向过程就是排着用最简单的代码一步一步写下去&#xff0c;没有封装&#xff0c;当业务复杂的时候&#xff0c;改动就很麻烦了 (2)&#xff…