转自:http://wenwen.soso.com/z/q307221773.htm
委托是C#中事件机制的核心,网上常常将委托神话了。将其比作一个门槛,过了的人很欢喜,没过的人在门外干着急,很无语。
本文将与你一同探讨委托的实现和观察者(Observer)设计模式的关系。
在学C#的时候,委托对于我就是一道门槛,而且是很高的门槛。我好几次都搁在门槛上,摔得头破血流。很久以来都对它产生恐惧感。网上文章看了不少,理解起来生硬费力,不是人家讲的不好,而是自己太浅薄了。而网上文章多以实例配合事件机制讲解,对于C++的程序员来说可能更好理解,说白了委托就是一种类似指针的东西。
在学习委托之前,我们来了解一下观察者设计模式,委托和时间很好的实现了观察者设计模式。举个在head first中的例子,然后再举个天气预告板的例子将它改造成C#中基于委托来实现。
出版社要出版一个杂志,为了更好适应这个市场,出版社是花样百出,结果很多读者都来订阅这个杂志了。我们把出版社看做是一个主题或者被观察者,读者看做观察者。当出版社更新杂志时,读者将得到通知,以获取最新的内容。因为杂志办的越来越火,因此吸引了越来越多的读者,新的读者想要获得杂志,那么必须向出版社订购,这个过程我们理解为注册。后来由读者A不满意了,就不想订购了,那么就提起撤销申请,接着出版社就把该读者A名单移除了,当新的一期杂志出炉时,就不会再通知该读者A来获得信息。一旦读者A无聊又想要杂志了,那么他必须重新订购才行。
从上面的例子中我们看出,出版社(主题或被观察者)有几个动作,接受一个读者的订购请求,移除一个读者,给每个读者都分发最新的杂志。
下面的代码演示一个没有基于委托的时候我们该怎么实现:
public class Public Publisher {
List scribers; //保存新增的读者
public Publisher() {
this.scribers = new ArrayList();
}
public void registerScriber(Scriber sc) {
scribers.add(sc);
}
public void removeScriber(Scriber sc) {
int index = scribers.indexof(sc);
if (index > 0)
scribers.remove(sc);
}
//当有新的杂志更新时将执行
public void newMagazine() {
for (int i = 0; i < scribers.length; i++) {
Scriber sc = (Scriber)scribers.get(i);
sc.update();
}
}
}
public class Scriber {
//包含一个指向主题的引用
private Publisher pub;
public Scriber(Publisher pub) {
this.pub = pub;
pub.registerScriber(this);//注册
}
public void update() {
Sysout.out.println("您有新的杂志啦!!");
}
}
public class Test{
public static void main(String[] args) {
Publisher pub = new Publisher();
Scriber sc = new Scriber(pub);
pub.newMagazine();
}
}
/*output:
您有新的杂志啦!!
*/
通过上面的一个读者和出版社的例子演示了一个简单的实现观察者模式。当新的杂志出来了,将通知每个读者,我们可以定义多个读者,也就是说出版社和读者的关系式1对多的关系。理解了观察者模式,在下面的例子中将使用C#的委托机制来完成一个天气预告板的例子。有点长。
一个气象台想要委托你的公司设计一个接口,可以实现不同的天气预告版,如当前天气板,统计板等等,这个接口必须能提供给其他开发商进行开发。气象台提供了一个WeatherData,当天气变化时,将会调用measurementsChanged()方法。因此我们设计的接口必须能够在天气变化时(即measurementsChanged时作出响应),动态的改变各个板块的信息。
如果没有使用委托机制来完成,那么必须像上面一个例子中来做,以到达效果。下面将用委托实现。
写一个Observer接口,包含一个update()方法,所有具体实现了该接口的类都必须实现update()方法,
using System;
using System.Collections.Generic;
using System.Text;
namespace ObserverPattern
{
interface Observer
{
/// <summary>
/// 当天气信息更新时,将调用该方法
/// </summary>
/// <param name="temp">温度</param>
/// <param name="humidity">湿度</param>
/// <param name="pressure">气压</param>
void update(object sender);
}
}
写一个DisplayElement接口,各个板块都必须实现该接口和Observer接口,包含一个display()方法
using System;
using System.Collections.Generic;
using System.Text;
namespace ObserverPattern
{
interface DisplayElement
{
/// <summary>
/// 显示天气信息
/// </summary>
void display();
}
}
写一个Subject接口,所有的主题都必须实现该接口
using System;
using System.Collections.Generic;
using System.Text;
namespace ObserverPattern
{
interface Subject
{
/// <summary>
/// 通知所有的观察者
/// </summary>
void notifyObservers();
}
}
写一个实现Subject接口的WeatherData类,作为具体的主题
using System;
using System.Collections.Generic;
using System.Text;
namespace ObserverPattern
{
//声明一个委托,委托相当于代理类,但它只代理某个方法,如下定义
//object sender 指的是触发事件的对象
public delegate void WeatherChanged(object sender);
class WeatherData: Subject
{
//这里是将委托和事件绑定
public event WeatherChanged OnWeatherChanged;
private float temperature;
private float humidity;
private float pressure;
/// <summary>
/// 设置状态,当更新完天气信息,设置false;
/// </summary>
private Boolean changed = false;
public void setChanged()
{
if (changed)
{
changed = false;
}
else
{
changed = true;
}
}
/// <summary>
/// 设置温度,湿度,气压值
/// </summary>
/// <param name="temperature"></param>
/// <param name="humidity"></param>
/// <param name="pressure"></param>
public void setMeasurements(float temperature, float humidity, float pressure)
{
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
/// <summary>
/// 度量信息变化,即天气信息变化
/// </summary>
public void measurementsChanged()
{
setChanged();
notifyObservers();
}
/// <summary>
/// 通知所有公告板
/// </summary>
public void notifyObservers()
{
if (OnWeatherChanged != null)
OnWeatherChanged(this);//触发事件
}
public float getTemperature()
{
return temperature;
}
public float getHumidity()
{
return humidity;
}
public float getPressure()
{
return pressure;
}
}
}
写一个具体的实现了Observer接口的观察者
using System;
using System.Collections.Generic;
using System.Text;
namespace ObserverPattern
{
class CurrentConditionsDisplay:Observer,DisplayElement
{
private float temperature;
private float pressure;
/// <summary>
/// 将该方法交给委托,该方法将由委托调用,该方法必须和委托拥有相同的签名,就是参数列表一致
/// </summary>
/// <param name="sender"></param>
public void update(object sender)
{
WeatherData weatherData = (WeatherData)sender;
this.temperature = weatherData.getTemperature();
this.pressure = weatherData.getPressure();
}
public void display()
{
Console.WriteLine("当前温度是: " + temperature + " 气压是: " + pressure);
}
}
}
再写一个具体的实现了Observer接口的观察者
using System;
using System.Collections.Generic;
using System.Text;
namespace ObserverPattern
{
class StatisticsDisplay:Observer,DisplayElement
{
private float maxTemp = 0.0f;
private float minTemp = 200;
private float tempSum = 0.0f;
//保存增加的次数,用于球平均值
private int numReadings;
/// <summary>
/// 将该方法交给委托,该方法必须和委托拥有相同的签名,就是参数列表一致
/// </summary>
/// <param name="sender"></param>
public void update(object sender)
{
WeatherData weatherData = (WeatherData)sender;
float temp = weatherData.getTemperature();
this.tempSum += temp;
this.numReadings++;
if (temp > maxTemp)
maxTemp = temp;
if (temp < minTemp)
minTemp = maxTemp;
}
public void display()
{
Console.WriteLine("Avg/Max/Min temperature = " + (tempSum / numReadings)
+ "/" + maxTemp + "/" + minTemp);
}
}
}
写一个测试类
using System;
using System.Collections.Generic;
using System.Text;
namespace ObserverPattern
{
class Program
{
static void Main(string[] args)
{
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditions = new CurrentConditionsDisplay();
StatisticsDisplay statisticsDisplay = new StatisticsDisplay();
//注册过程
weatherData.OnWeatherChanged += new WeatherChanged(currentConditions.update);
weatherData.OnWeatherChanged += new WeatherChanged(statisticsDisplay.update);
weatherData.setMeasurements(80, 85, 32.0f);
currentConditions.display();
statisticsDisplay.display();
weatherData.setMeasurements(82, 85, 32.0f);
currentConditions.display();
statisticsDisplay.display();
Console.Read();
}
}
}
运行结果:
当前温度是: 80 气压是: 32
Avg/Max/Min temperature = 80/80/80
当前温度是: 82 气压是: 32
Avg/Max/Min temperature = 81/82/80
一个委托的实例就完成了,流程是先声明一个委托,然后声明一个事件和委托绑定,接着写一个和委托具有相同签名的方法,(委托要做的工作就是代理某个方法),并将该方法托付给委托,最后由委托进行注册,将事件,委托,被委托的方法捆绑一起。当触发事件时将去找委托,委托又去找被委托的方法,这个过程中,委托一致充当的是代理人的角色。因C#没有开源,我们无从了解更加底层的代码。可以想象的是委托在事件触发时,将得到传递的对象引用,并传递给被委托的方法,两者之间的协作。作为一个桥梁,委托一手连接被观察者,一手连接观察者。
C#委托在提供便利的同时,也让人头痛不已。无法去了解更加底层的东西,要是没有学过观察者设计模式,我现在还是一头雾水。