Downloader.cs
class Downloader
{
public void Download(string resource)
{
for (int i = 1, size = 10; i this.OnProrgess(i, size);
Thread.Sleep(500);
}
}
public void AddMonitor(IProgressMonitor monitor)
{
this._monitors.Add(monitor);
}
public void RemoveMonitor(IProgressMonitor monitor)
{
this._monitors.Remove(monitor);
}
private void OnProrgess(int done, int total)
{
foreach (var i in this._monitors)
{
i.OnProgress(done, total);
}
}
private ICollection _monitors = new List();
//进度监视者集合
}
Program.cs
class Program
{
static void Main(string[] args)
{
var resouce = "visual studio.iso";
var downloader = new Downloader();
downloader.AddMonitor(new ProgressVeiw());
Console.WriteLine("正在下载 " + resouce);
downloader.Download(resouce);
Console.ReadKey();
}
}
  整个逻辑是这样的:
  创建下载器
  ProgressView对下载进度“感兴趣”,到下载器那“登记一下”
  下载器开始下载
  下载器下载过程进度有变化时通知已登记了的对象,具体这些对象要干嘛,它不管,它只负责通知
  ProgressView收到通知,更新界面
  从代码中可以看到,实现“登记”、“通知”是通过手动操作一个集合实现的,其实这种功能.Net已经帮我们实现,那是多播委托。
  3.1 多播委托(MulticastDelegate)
  委托类层次图

  .Net中所有具体委托类型其实都继承自MulticastDelegate,多播委托内部有一个数组,用来保存其它委托,这是所谓的“委托链”,有了它可以对多个委托进行组合,让一个委托可以一次执行多个操作。比如:
  Action act = ()=>Console.WriteLine("动作1");
  act += ()=>Console.WriteLine("动作2");
  act += ()=>Console.WriteLine("动作3");
  act();
  //将打印出三行文字
  这里编译器其实把+=操作符替换成了Delegate.Combine(Delegate a, Delegate b)方法,该方法内部又是直接调用第一个参数的CombineImpl方法。有兴趣的可以阅读其源码。
  因此多播委托的作用在于,现实观察者模式不用手动维护一个订阅者集合了。于是上面的例子可以重构成这样:
  Downloader.cs
class Downloader
{
public void Download(string resource)
{
for (int i = 1, size = 10; i this.OnProrgess(i, size);
Thread.Sleep(500);
}
}
//因为有多播委托,所以这里不需要“登记”、“注销”方法了
private void OnProrgess(int done, int total)
{
if (this.DownloadProgress != null)
this.DownloadProgress(done, total);
}
//NOTE: 这里为了用 +=,-=操作符替代“登记”、“注销”方法,不得以将订阅者集合暴露出去
public Actionint, int> DownloadProgress;
//进度回调链
}
删除IProgressMonitor接口,因为只需要委托签名匹配即可,不需要用接口来约束
  Program.cs
class Program
{
static void Main(string[] args)
{
var resouce = "visual studio.iso";
var downloader = new Downloader();
downloader.DownloadProgress += new ProgressVeiw().OnProgress;
Console.WriteLine("正在下载 " + resouce);
downloader.Download(resouce);
Console.ReadKey();
}
}
  注意上面Downloader.cs注释中的NOTE标记,在观察者模式中,发布者应该是它自己发布消息,而此处是public修饰,可以直接在外部调用,违背了观察者模式,因此我们需要使用事件来进行访问控制。
  3.2 事件
  方法相当简单,只需要添加一个event关键字
  //用event修饰,这样外部只能进行-=、+=操作,调用只能在本类内部进行
  public event Actionint, int> DownloadProgress;
  //进度事件
  那事件和委托有什么区别?其实事件和属性一样,属语法糖,编译器会为我们生成访问器方法和实际的实例变量。
  比如上面,编译器生成了Action成员,把事件的+=,-=转换为一对public的addXxx、removeXxx方法。
  如果不想让编译器为我们自动生成委托成员,我们也可以手动实现,如:
  public event Actionint, int> DownloadProgress
  {
  add
  {
  _downloadProgress += value;
  }
  remove
  {
  _downloadProgress -= value;
  }
  }
  private Actionint, int> _downloadProgress;
  所以事件的作用在于,实现观察者模式只需要用event关键字定义一个实例变量即可
  其它语言相关概念
  C++
  C/C++中的函数指针与.Net中委托的区别在于
  函数指针很“赤裸”,是一个unsigned int类型的值,表示函数第一条CPU指令的内存地址
  委托是比较复杂的CLR对象,内部封装了目标对象和方法指针,多播委托内部还维护了一个委托链
  C++虽然没有内置观察者模式的实现,但许多第三方库如qt、boost都提供了“信号(signal)-槽(slot)”的功能。信号相当于.Net中的事件,槽相当于事件处理方法。
  使用boost的signals2时需要注意的是,它实现的线程安全是指一个线程中添加、移除slot,不会影响另一个线程遍历slot集合。我们仍要注意对象被销毁的问题,比如一个slot在某线程中正在执行,而同时signal所属的那个对象在另一个线程被销毁了,这时访问那个对象会不会导致程序crash要看运气了。
  Java
  java没有匿名方法的语法,但可以用”匿名内部类 + 单方法接口”代替,java8对语法和api进行了扩展,引入了lambda,其实只是语法糖,本质还是接口。
  有一些第三方类库提供了观察者模式的实现,比如:谷歌guava中的EventBus。
  Ruby
  ruby语法非常丰富,这里介绍下它的block(代码块,一种匿名方法的语法:do |param| ...end 和 { |param| do_something param },这个概念来自Smalltalk)。
  我们在写程序时会经常需要实现“回调”的功能,即接收一个函数,然后在适当的时刻调用它,ruby直接原生支持了,任何方法都可以接收一个block作为隐式的参数,不用在参数列表中显式声明,在方法里可以用yield关键字调用传进来的block。如:
  def process_data data
  do_work(data) # 假装在处理数据
  yield data if block_given? # 如果有block则调用它
  end
  process_data([1, 2, 3]) do |data|
  puts '处理完成!'
  end
  再比如核心库中的File::open方法
  # 调用该方法时如果
  #     传入block则将打开的文件传给block,本方法结束后文件对象被销毁,这样不用麻烦调用者关闭文件了
  # 否则
  #     返回文件对象
  File.open('file.txt') do |f|
  puts f.readlines # 打印出所有行
  end
  再看ruby的一个web框架Sinatra的API
  get '/home/index' do
  '我是对get请求的响应'
  end
  post '/home/index' do
  '我是对post请求的响应'
  end
  用这种语法来写HTTP Web应用是不是很爽?因为这种API设计可以算是实现了一种HTTP的DSL(领域专用语言)了。
  如果你喜欢这种风格,可以试试.Net的开源web框架Nancy,我已用她写了两个项目,API很实用,比ASP.NET MVC灵活。
  这里简单展示一下她的特色:content negotiation(内容协商,根据客户端请求返回指定类型内容)
  Get["/"] = parameters => {
  //这里返回的对象会被传递到nancy的content negotiation管道
  //然后会检查请求头的accept类型,比如 如果是xml则该对象被序列化为xml,如果是json则被序列为json
  return new MyModel();
  };