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( " [email protected] " );       
 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( " [email protected] " );
 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( " [email protected] " ));
 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( " [email protected] " ,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( " [email protected] " );
 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
拉模式和推模式的差别在概念上讲得很清楚,但是,从代码上没看出推和拉的差别来/

  

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

  
 
#13楼  2009-04-17 14:34  soxunyi
public void update(Observable obs, Object arg) 
这是Java中Obersver接口方法, 
如果在方法体中通过obs来获取主题的状态变化,则是拉模式; 
如果在方法体中通过arg来获取主题的状态变化,则是推模式。 
因为,通过obs获取是在观察者类中实现的,故为拉。 
而arg则是由主题类传递给观察者的,故为推。
发布了482 篇原创文章 · 获赞 42 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/sinolover/article/details/104054727
今日推荐