First we try, then we trust

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  183 随笔 :: 111 文章 :: 3312 评论 :: 358 引用

一、 观察者(Observer)模式

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

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

一个软件系统常常要求在某一个对象的状态发生变化的时候,某些其它的对象做出相应的改变。做到这一点的设计方案有很多,但是为了使系统能够易于复用,应该选择低耦合度的设计方案。减少对象之间的耦合有利于系统的复用,但是同时设计师需要使这些低耦合度的对象之间能够维持行动的协调一致,保证高度的协作(Collaboration)。观察者模式是满足这一要求的各种设计方案中最重要的一种。


二、 观察者模式的结构

观察者模式的类图如下:

 

可以看出,在这个观察者模式的实现里有下面这些角色:

  • 抽象主题(Subject)角色:主题角色把所有对观察考对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,主题角色又叫做抽象被观察者(Observable)角色,一般用一个抽象类或者一个接口实现。
  • 抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者角色一般用一个抽象类或者一个接口实现。在这个示意性的实现中,更新接口只包含一个方法(即Update()方法),这个方法叫做更新方法。
  • 具体主题(ConcreteSubject)角色:将有关状态存入具体现察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者角色(Concrete Observable)。具体主题角色通常用一个具体子类实现。
  • 具体观察者(ConcreteObserver)角色:存储与主题的状态自恰的状态。具体现察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体现察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。

从具体主题角色指向抽象观察者角色的合成关系,代表具体主题对象可以有任意多个对抽象观察者对象的引用。之所以使用抽象观察者而不是具体观察者,意味着主题对象不需要知道引用了哪些ConcreteObserver类型,而只知道抽象Observer类型。这就使得具体主题对象可以动态地维护一系列的对观察者对象的引用,并在需要的时候调用每一个观察者共有的Update()方法。这种做法叫做"针对抽象编程"。


三、 观察者模式的示意性源代码

// Observer pattern -- Structural example  
using System;
using System.Collections;

// "Subject"
abstract class Subject
{
  
// Fields
  private ArrayList observers = new ArrayList();

  
// Methods
  public void Attach( Observer observer )
  
{
    observers.Add( observer );
  }


  
public void Detach( Observer observer )
  
{
    observers.Remove( observer );
  }


  
public void Notify()
  
{
    
foreach( Observer o in observers )
      o.Update();
  }

}


// "ConcreteSubject"
class ConcreteSubject : Subject
{
  
// Fields
  private string subjectState;

  
// Properties
  public string SubjectState
  
{
    
getreturn subjectState; }
    
set{ subjectState = value; }
  }

}


// "Observer"
abstract class Observer
{
  
// Methods
  abstract public void Update();
}


// "ConcreteObserver"
class ConcreteObserver : Observer
{
  
// Fields
  private string name;
  
private string observerState;
  
private ConcreteSubject subject;

  
// Constructors
  public ConcreteObserver( ConcreteSubject subject,  
    
string name )
  
{
    
this.subject = subject;
    
this.name = name;
  }


  
// Methods
  override public void Update()
  
{
    observerState 
= subject.SubjectState;
    Console.WriteLine( 
"Observer {0}'s new state is {1}",
      name, observerState );
  }


  
// Properties
  public ConcreteSubject Subject
  
{
    
get return subject; }
    
set { subject = value; }
  }

}


/// <summary>
/// Client test
/// </summary>

public class Client
{
  
public static void Main( string[] args )
  
{
    
// Configure Observer structure
    ConcreteSubject s = new ConcreteSubject();
    s.Attach( 
new ConcreteObserver( s, "1" ) );
    s.Attach( 
new ConcreteObserver( s, "2" ) );
    s.Attach( 
new ConcreteObserver( s, "3" ) );

    
// Change subject and notify observers
    s.SubjectState = "ABC";
    s.Notify();
  }

}


四、 C#中的Delegate与Event

实际上在C#中实现Observer模式没有这么辛苦,.NET中提供了Delegate与Event机制,我们可以利用这种机制简化Observer模式。关于Delegate与Event的使用方法请参考相关文档。改进后的Observer模式实现如下:

// Observer pattern -- Structural example  
using System;

//Delegate
delegate void UpdateDelegate(); 

//Subject
class Subject
{
  
public event UpdateDelegate UpdateHandler;
  
  
// Methods
  public void Attach( UpdateDelegate ud )
  
{
    UpdateHandler 
+= ud;
  }


  
public void Detach( UpdateDelegate ud )
  
{
    UpdateHandler 
-= ud;
  }

  
  
public void Notify()
  
{
    
if(UpdateHandler != null) UpdateHandler();
  }


}


//ConcreteSubject
class ConcreteSubject : Subject
{
  
// Fields
  private string subjectState;

  
// Properties
  public string SubjectState
  
{
    
getreturn subjectState; }
    
set{ subjectState = value; }
  }

}


// "ConcreteObserver"
class ConcreteObserver
{
  
// Fields
  private string name;
  
private string observerState;
  
private ConcreteSubject subject;

  
// Constructors
  public ConcreteObserver( ConcreteSubject subject,  
    
string name )
  
{
    
this.subject = subject;
    
this.name = name;
  }


  
// Methods
  public void Update()
  
{
    observerState 
= subject.SubjectState;
    Console.WriteLine( 
"Observer {0}'s new state is {1}",
      name, observerState );
  }


  
// Properties
  public ConcreteSubject Subject
  
{
    
get return subject; }
    
set { subject = value; }
  }

}


// "ConcreteObserver"
class AnotherObserver
{
  
// Methods
  public void Show()
  
{
    Console.WriteLine(
"AnotherObserver got an Notification!");
  }

}


public class Client

  
public static void Main(string[] args)
  

    ConcreteSubject s 
= new ConcreteSubject();
    ConcreteObserver o1 
= new ConcreteObserver(s, "1");
    ConcreteObserver o2 
= new ConcreteObserver(s, "2");
    AnotherObserver o3 
= new AnotherObserver();
    
    s.Attach(
new UpdateDelegate(o1.Update));
    s.Attach(
new UpdateDelegate(o2.Update));
    s.Attach(
new UpdateDelegate(o3.Show));

    s.SubjectState 
= "ABC";
    s.Notify();

    Console.WriteLine(
"--------------------------");
    s.Detach(
new UpdateDelegate(o1.Update));

    s.SubjectState 
= "DEF";
    s.Notify();
  }

}

其中,关键的代码如下:

delegate void UpdateDelegate(); 

定义一个Delegate,用来规范函数结构。不管是ConcreteObserver类的Update方法还是AnotherObserver类的Show方法都符合该Delegate。这不象用Observer接口来规范必须使用Update方法那么严格。只要符合Delegate所指定的方法结构的方法都可以在后面被事件所处理。

public event UpdateDelegate UpdateHandler;

定义一个事件,一旦触发,可以调用一组符合UpdateDelegate规范的方法。

  public void Attach( UpdateDelegate ud )
  
{
    UpdateHandler 
+= ud;
  }

订阅事件。只要是一个满足UpdateDelegate的方法,就可以进行订阅操作(如下所示)。

    s.Attach(new UpdateDelegate(o1.Update));
    s.Attach(
new UpdateDelegate(o2.Update));
    s.Attach(
new UpdateDelegate(o3.Show));

在Notify方法中:

  public void Notify()
  
{
    
if(UpdateHandler != null) UpdateHandler();
  }

只要UpdateHandler != null(表示有订阅者),就可以触发事件(UpdateHandler()),所有的订阅者便会接到通知。


五、 一个实际应用观察者模式的例子

该例子演示了注册的投资者在股票市场发生变化时,可以自动得到通知。该例子仍然使用的是传统的Observer处理手段,至于如何转换成Delegate与Event留给读者自己考虑。

// Observer pattern -- Real World example  
using System;
using System.Collections;

// "Subject"
abstract class Stock
{
  
// Fields
  protected string symbol;
  
protected double price;
  
private ArrayList investors = new ArrayList();

  
// Constructor
  public Stock( string symbol, double price )
  
{
    
this.symbol = symbol;
    
this.price = price;
  }


  
// Methods
  public void Attach( Investor investor )
  
{
    investors.Add( investor );
  }


  
public void Detach( Investor investor )
  
{
    investors.Remove( investor );
  }


  
public void Notify()
  
{
    
foreach( Investor i in investors )
      i.Update( 
this );
  }


  
// Properties
  public double Price
  
{
    
getreturn price; }
    
set
    
{
      price 
= value;
      Notify(); 
    }

  }


  
public string Symbol
  
{
    
getreturn symbol; }
    
set{ symbol = value; }
  }

}


// "ConcreteSubject"
class IBM : Stock
{
  
// Constructor
  public IBM( string symbol, double price )
    : 
base( symbol, price ) {}
}


// "Observer"
interface IInvestor
{
  
// Methods
  void Update( Stock stock );
}


// "ConcreteObserver"
class Investor : IInvestor
{
  
// Fields
  private string name;
  
private string observerState;
  
private Stock stock;

  
// Constructors
  public Investor( string name )
  
{
    
this.name = name;
  }


  
// Methods
  public void Update( Stock stock )
  
{
    Console.WriteLine( 
"Notified investor {0} of {1}'s change to {2:C}"
      name, stock.Symbol, stock.Price );
  }


  
// Properties
  public Stock Stock
  
{
    
getreturn stock; }
    
set{ stock = value; }
  }

}


/// <summary>
/// ObserverApp test
/// </summary>

public class ObserverApp
{
  
public static void Main( string[] args )
  
{
    
// Create investors
    Investor s = new Investor( "Sorros" );
    Investor b 
= new Investor( "Berkshire" );

    
// Create IBM stock and attach investors
    IBM ibm = new IBM( "IBM"120.00 );
    ibm.Attach( s );
    ibm.Attach( b );

    
// Change price, which notifies investors
    ibm.Price = 120.10;
    ibm.Price 
= 121.00;
    ibm.Price 
= 120.50;
    ibm.Price 
= 120.75;
  }

}


六、 观察者模式的优缺点

Observer模式的优点是实现了表示层和数据逻辑层的分离,并定义了稳定的更新消息传递机制,类别清晰,并抽象了更新接口,使得可以有各种各样不同的表示层(观察者)。

但是其缺点是每个外观对象必须继承这个抽像出来的接口类,这样就造成了一些不方便,比如有一个别人写的外观对象,并没有继承该抽象类,或者接口不对,我们又希望不修改该类直接使用它。虽然可以再应用Adapter模式来一定程度上解决这个问题,但是会造成更加复杂烦琐的设计,增加出错几率。

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

(1)观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体现察者聚集,每一个具体现察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。

(2)观察者模式支持广播通信。被观察者会向所有的登记过的观察者发出通知。

观察者模式有下面的一些缺点:

(1)如果一个被观察者对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

(2)如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察考模式时要特别注意这一点。

(3)如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。

(4)虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。



参考文献:
阎宏,《Java与模式》,电子工业出版社
[美]James W. Cooper,《C#设计模式》,电子工业出版社
[美]Alan Shalloway  James R. Trott,《Design Patterns Explained》,中国电力出版社
[美]Robert C. Martin,《敏捷软件开发-原则、模式与实践》,清华大学出版社
[美]Don Box, Chris Sells,《.NET本质论 第1卷:公共语言运行库》,中国电力出版社

 

posted on 2004-12-07 08:36 吕震宇 阅读(26155) 评论(21)  编辑 收藏

评论

#1楼 2004-12-07 09:20 Koffer
//Delegate
delegate void UpdateDelegate();

这个地方要加上 public ! 如果你每个class在一个单独的文件
 回复 引用   

#2楼 2004-12-07 12:13 montaque
你举一个 remoting 应用中例子就更合适了。
 回复 引用   

#3楼[楼主] 2004-12-07 16:49 吕震宇      
@Koffer、montaque

谢谢!我这里的例子主要是教学目的,而且很多都是网上照搬的,所以难免有些不贴切的地方。我会在不久推出一个《华容道与数据结构》以及《华容道与设计模式》系列,希望能不负众望。
 回复 引用 查看   

#4楼 2004-12-28 22:47 81      
在用Delegate的方案中,subject只是加入了每个Observer
对象中一个方法的句柄,不知是否会出现这种情况:一个Observer被释放,但subject仍保存这个Observer对象中的这个方法的句柄?即引用了一个已释放的地址。

不知我的表达是否清楚。
 回复 引用 查看   

#5楼 2004-12-28 23:13 81      
并且在Observer中提供了更改被观察对象的方法:

// Properties
public ConcreteSubject Subject
{
get { return subject; }
set { subject = value; }
}
这里是不是应该只要get不要set呢?
我最近有空想把你的这个系列好好看一遍。
 回复 引用 查看   

#6楼[楼主] 2004-12-29 10:00 吕震宇      
@81

对于你提出的第一个问题,我实在不敢贸然回答。因为我对对象的垃圾回收机制并不是非常清楚。但就我的感觉是.net的垃圾回收机制是根据引用树来进行回收的。如果subject引用了Observer,那么在垃圾回收时该Objserver是不能被释放的。如果希望释放Observer,就必须释放其引用,也就是用subject的Detach方法。这样一来,Delegate的引用也就自然释放了。这是我的理解。

至于第二个问题,我想你说的是SubjectState属性。如果是ConcreteSubject属性就让我感到有些迷惑了。其实这段代码是照搬过来的。你这一说还提醒了我。我认为Observer模式本意是指,观察者观察SubjectState属性,一旦SubjectState属性发生变化,观察者应当自动得到通知。因此,客户端只要给SubjectState设置一个新值,所有观察者就自动得到通知了。我的想法是set方法不要去掉,而是在其中增加一行命令Notify(); ,这样一来,客户端的Notify命令便可省去。至于谁来改SubjectState都可以,包括Observer本身。设想Windows网络纸牌游戏,一个人出牌后其他人都得到通知。所以玩牌的人既是Observer,也是修改SubjectState触发Notify的人。不知道你是否赞同我的理解。
 回复 引用 查看   

#7楼 2004-12-29 12:37 81      
第一个问题大概是你说的这样。
第二个问题我说的的确是ConcreteSubject属性,因为一个Observer的subject是在构造函数中指定的,并且做了一些处理,而在ConcreteSubject属性的Set中只是简单的给一个值,这将使Observer失去原来的subject,也就无法得到准确的SubjectState,只能得到新subject的SubjectState(也就是说旧subject发通知,却得到新subject的值),所以我说这里不要set或不能简单的set。

你说的在subjectState的set方法加一个Notify()确实是个好方法。
 回复 引用 查看   

#8楼 2005-08-08 16:05 zitiger      
该例子演示了注册的投资者在股票市场发生变化时,可以自动得到通知。 public double Price
 public double Price
  {
    get{ return price; }
    set
    { 
      price = value;
      Notify();
    }
  }

这里说到发生变化,把
Notify();
改成这样应该更好,
if(price != value)        //price变化了.
{
    Notify();
}
 
感觉自己有点钻牛角了,哈哈.
 回复 引用 查看   

#9楼 2005-08-08 16:12 zitiger      
您在文中提到"但是其缺点是每个外观对象必须继承这个抽像出来的接口类"

这个感觉在.net不是这样,您的第二个例子中的AnotherObserver就没有继承任何类.因为.net不需要通过Base.Method()去做,而是由代理和事件来完成.
 回复 引用 查看   

个人感觉lz的Delegate实现代码没有完全体现Delegate的好处. 感觉它与第一种实现方法没带来什么较大的好处,方便.

个人理解为观察者模式本质上就是用来做发布-订阅,实现两个对象的通信.
因此用Delegate完全可以省略调Subject类, 我们将UpdateHandler申明在ConcreteSubject 中, 在Main()中注册ConcreteObserver 方法.

还有您说到的:Observer模式的优点是实现了表示层和数据逻辑层的分离.不太理解这句话,在这里如何体现呢?
个人感觉Observer模式的最大好处就是ConcreteSubject对象根本不用关心它的状态发生变化时要去通知谁,实现他们之间的脱偶

以上为个人不成熟的观点,还望指正
 回复 引用   

bucuo
 回复 引用   

很受用~~谢谢博主~~~
 回复 引用   

#13楼 2008-01-29 11:25 xglu[未注册用户]
一个问题:如果有很多的被观察者对象,我是不是需要一个一个的为他们添加观察者呢?
 回复 引用   

#14楼 2008-07-27 11:40 chunfeng      
问老师一个问题,看到你主题角色里面都没有写方法

是不是不能在那里写方法呀
 回复 引用 查看   

您的UML图是用什么工具画的?
 回复 引用   

#16楼 2010-03-06 11:16 晓风残月      
楼主的 C#/Delegate Demo 中没有体现 .NET Event 的优势,似乎仅仅是将接口的实现以 delegate 的方式实现了,并且 observer 与 subject 耦合在一起了(实例化 observer 需要 subject),如果 subject 作为参数传递给 observer.update 应该灵活一些,.NET Event 也是用此方式来实现
 回复 引用 查看