观察者模式可以用来同时以多种方式表示数据。我们可能有一组数据,然后希望同时在用户界面用多种表示方式显示这组数据,比如用表格和绘图的两种方式。并且当数据发生变化的时候我们希望数据的显示能够自动的更新,这就需要我们使用观察者模式。
观察者模式假设包含数据的对象与显示的对象是分离的,显示对象就需要观察数据中的变化。
在实现观察者模式的时候,我们通常把数据成为主题(Subject),把每种显示方式成为观察者(Observer).
观察者都有一个为其他对象所知的接口,这样当数据放生变化的时候主题就可以通过调用这个接口告知观察者数据的变化。
观察者统一的接口定义为:
using System;
namespace Observer
{
/// <summary>
/// Summary description for Observer.
/// </summary>
public interface Observer {
void sendNotify(string message);
}
}
主题的接口:
using System;
namespace Observer
{
/// <summary>
/// Summary description for Subject.
/// </summary>
public interface Subject {
void registerInterest(Observer obs);
}
}
我们的一个简单例子,在主程序面板中有三个颜色选择按钮,单击一个按钮在另外两个面板中会显示对应的颜色文字和对应的颜色。这样就需要两个显示面板作为观察者观察主面板这个主题中的数据的变化。
程序的截图如下:
这样,主窗口在这里就是我们的主题,主题的定义如下:
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
namespace Observer
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form, Subject
{
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.RadioButton opRed;
private System.Windows.Forms.RadioButton opGreen;
private System.Windows.Forms.RadioButton opBlue;
private ArrayList observers;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public Form1()
{
InitializeComponent();
init();
}
private void init() {
EventHandler evh = new EventHandler (opButton_Click);
opRed.Click += evh;
opBlue.Click += evh;
opGreen.Click += evh;
observers = new ArrayList ();
ListObs lobs = new ListObs (this);
lobs.Show ();
ColObserver colObs = new ColObserver (this);
colObs.Show();
}
public void registerInterest(Observer obs ) {
observers.Add (obs);
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.opBlue = new System.Windows.Forms.RadioButton();
this.opGreen = new System.Windows.Forms.RadioButton();
this.opRed = new System.Windows.Forms.RadioButton();
this.groupBox1.SuspendLayout();
this.SuspendLayout();
//
// groupBox1
//
this.groupBox1.Controls.Add(this.opBlue);
this.groupBox1.Controls.Add(this.opGreen);
this.groupBox1.Controls.Add(this.opRed);
this.groupBox1.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.groupBox1.Location = new System.Drawing.Point(38, 26);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(192, 155);
this.groupBox1.TabIndex = 0;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "Select colors";
//
// opBlue
//
this.opBlue.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.opBlue.ForeColor = System.Drawing.Color.Blue;
this.opBlue.Location = new System.Drawing.Point(38, 95);
this.opBlue.Name = "opBlue";
this.opBlue.Size = new System.Drawing.Size(96, 17);
this.opBlue.TabIndex = 2;
this.opBlue.Text = "Blue";
//
// opGreen
//
this.opGreen.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.opGreen.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(192)))), ((int)(((byte)(0)))));
this.opGreen.Location = new System.Drawing.Point(38, 69);
this.opGreen.Name = "opGreen";
this.opGreen.Size = new System.Drawing.Size(116, 17);
this.opGreen.TabIndex = 1;
this.opGreen.Text = "Green";
//
// opRed
//
this.opRed.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.opRed.ForeColor = System.Drawing.Color.Red;
this.opRed.Location = new System.Drawing.Point(38, 34);
this.opRed.Name = "opRed";
this.opRed.Size = new System.Drawing.Size(125, 26);
this.opRed.TabIndex = 0;
this.opRed.Text = "Red";
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
this.ClientSize = new System.Drawing.Size(293, 218);
this.Controls.Add(this.groupBox1);
this.Name = "Form1";
this.Text = "Observer demo";
this.groupBox1.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private void opButton_Click(object sender, System.EventArgs e) {
RadioButton but = (RadioButton)sender;
for(int i=0; i< observers.Count ; i++ ) {
Observer obs = (Observer)observers[i];
obs.sendNotify (but.Text );
}
}
}
}
主题中定义了两个观察者的实例,并把自身的引用传递给观察者,这样在观察者构造的时候可以在主程序的主题中注册观察者。这里的主题在处理按钮事件的时候,找到对应的按钮事件,然后给注册的所有观察者发送数据变化通知,这样观察者就可以做出相应的反映。
两个观察者的定义:
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
namespace Observer
{
/// <summary>
/// Summary description for ListObs.
/// </summary>
public class ListObs : System.Windows.Forms.Form, Observer
{
private System.Windows.Forms.ListBox lsColors;
/// <summary>
/// Adds text of color to list box
/// </summary>
private System.ComponentModel.Container components = null;
public ListObs(Subject subj) {
InitializeComponent();
init(subj);
}
//------
public void init(Subject subj) {
subj.registerInterest (this);
}
//------
public void sendNotify(string message){
lsColors.Items.Add(message);
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.lsColors = new System.Windows.Forms.ListBox();
this.SuspendLayout();
//
// lsColors
//
this.lsColors.ItemHeight = 12;
this.lsColors.Location = new System.Drawing.Point(19, 17);
this.lsColors.Name = "lsColors";
this.lsColors.Size = new System.Drawing.Size(259, 184);
this.lsColors.TabIndex = 0;
//
// ListObs
//
this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
this.ClientSize = new System.Drawing.Size(299, 222);
this.Controls.Add(this.lsColors);
this.Name = "ListObs";
this.Text = "List observer";
this.ResumeLayout(false);
}
#endregion
}
}
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
namespace Observer
{
/// <summary>
/// Summary description for ColObserver.
/// </summary>
public class ColObserver : System.Windows.Forms.Form, Observer {
private System.ComponentModel.Container components = null;
private Brush bBrush;
private System.Windows.Forms.PictureBox pic;
private Font fnt;
private Hashtable colors;
private string colName;
//-----
public ColObserver(Subject subj) {
InitializeComponent();
init(subj);
}
//-----
private void init(Subject subj) {
subj.registerInterest (this);
fnt = new Font("arial", 18, FontStyle.Bold);
bBrush = new SolidBrush(Color.Black);
pic.Paint+= new PaintEventHandler (paintHandler);
colors = new Hashtable ();
colors.Add("red", Color.Red );
colors.Add ("blue", Color.Blue );
colors.Add ("green", Color.Green );
colName = "";
}
//-----
public void sendNotify(string message) {
colName = message;
message = message.ToLower ();
Color col = (Color)colors[message];
pic.BackColor = col;
}
//-----
private void paintHandler(object sender, PaintEventArgs e) {
Graphics g = e.Graphics ;
g.DrawString(colName, fnt, bBrush, 20, 40);
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.pic = new System.Windows.Forms.PictureBox();
((System.ComponentModel.ISupportInitialize)(this.pic)).BeginInit();
this.SuspendLayout();
//
// pic
//
this.pic.BackColor = System.Drawing.SystemColors.ActiveCaptionText;
this.pic.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
this.pic.Location = new System.Drawing.Point(29, 26);
this.pic.Name = "pic";
this.pic.Size = new System.Drawing.Size(221, 164);
this.pic.TabIndex = 0;
this.pic.TabStop = false;
//
// ColObserver
//
this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
this.ClientSize = new System.Drawing.Size(287, 218);
this.Controls.Add(this.pic);
this.Name = "ColObserver";
this.Text = "Color observer";
((System.ComponentModel.ISupportInitialize)(this.pic)).EndInit();
this.ResumeLayout(false);
}
#endregion
}
}
这两个观察者在收到数据变化通知以后,就做出相应的反映,改变用户界面,改变数据的显示。
观察者促进了到主题的抽象耦合。主题并不知道其任何观察者的细节。不过,观察者模式也有一个缺点,当数据发生了一些列的增量改变的时候,观察者就会收到连续的数据变化通知并做出反复的更新。如果这些更新的成本很高,则引入某种变更管理就是很有必要的了,这样观察者就不会太快或者太过于频繁的收到数据变化通知。