设计模式初学者系列-观察者模式2007-11-01 15:37 by 横刀天笑, 2982 visits, 收藏, 编辑 本系列文章目录 本文章首发在IT168技术频道,您可以点击这里查看摘要 在今天的设计模式系列中我给大家带来了观察者模式,首先我会以一个生动的故事引入观察者模式的应用的场景,然后描述这个场景中出现的问题,最后我们提出观察者模式的解决方案,并给出C#语言实现的代码,最后以.net里的委托-事件方式的观察者模式作为结尾。 故事
看到这么多烦恼,我们创意无限的Nokia公司给小雪和男孩们提出了解决方案: 观察者模式的解决方案 在上面Nokia的解决方案中就透露出观察者模式的思想:观察者模式定义了对象之间一对多的依赖,当这个对象的状态发生改变的时候,多个对象会接受到通知,有机会做出反馈。在运行的时刻可以动态的添加和删除观察者。 带着这个定义我们来看看尝试实现上面的观察者模式 首先在观察者模式中我们必须定义一个所有“观察者”都必须实现的接口,这样被观察者向观察者发送消息的时候就可以使用统一的方式,这也符合面相对象原则中的面向接口编程: 1//所有观察者都必须实现
2public interface IBoy 3{ 4//向男孩们显示小雪位置情况,也就是向观察者发送消息,观察者还可以对此做出反馈 5void Show(string address); } 6 7using System; 8//男孩A,一个观察者 9public class BoyA : IBoy 10{ 11public void Show(string address) 12{ 13//假设经过处理后为韩文的地址 14Console.WriteLine("A:"+address); 15} 16} 17using System; 18//男孩B,又一个观察者 19public class BoyB : IBoy 20{ 21public void Show(string address) 22{ 23//假设经过处理后为英语的地址 24Console.WriteLine("B:"+address); 25} 26} 下面看看小雪的实现,也就是被观察者,主要看看那个订阅的电话列表和怎样将消息通知给观察者. 1using System;
2using System.Collections.Generic; 3public class GPRSMobile 4{ 5//保存一个观察者列表 6private List<IBoy> boys = null; 7private string address = ""; 8public GPRSMobile() 9{ 10boys = new List<IBoy>(); 11} 12//添加观察者 13public void AddBoy(IBoy b) 14{ 15boys.Add(b); 16} 17public void RemoveBoy(IBoy b) 18{ 19boys.Remove(b); 20} 21//通知 22private void Notify(string address) 23{ 24for (int i = 0; i < boys.Count; i++) 25{ 26boys[i].Show(address); 27} 28} 29//位置发生变化 30public void OnAddressChanaged(string newAddress) 31{ 32//假设这里的地址是中文形式的 33Notify(newAddress); 34} 35} 看到上面的代码例子,我们可以给观察者模式的实现总结出这样几条规律:第一,被观察者必须保存着一个观察者列表。第二,所有的观察者必须实现一个统一的接口。 .net中的观察者模式
在.net中,微软给我们带来一个更好的观察者模式的实现:事件-委托. 在Gof的观察者模式中(姑且称之为经典设计模式吧),观察者必须实现一个统一的接口,在.net里这个接口由委托的签名来保证了,.net里的委托就是 一个安全的函数指针(之所以说安全是与以前的C指针相比的,C的函数指针并不包括函数的签名比如参数等东西,所以可以传递一个并不是你期望的函数进去,导 致运行时出错,由于这种错误在运行时发生,很难检查出来)。Ok,现在以一个.net的委托-事件的例子结束今天的观察者模式吧。 描述:这是一个控制台程序,程序接收一个0到100之间整型的输入,程序接收到输入后开始一个从0到100的循环,当循环到你输入的数字的时候做一些处理,我们将以两种方式来描述这个实例,先用常规的方式,然后采用委托-事件的方式 1public class Program
2{ 3static void Main(string[] args) 4{ 5Console.WriteLine("Please Input a 0-100 Number:"); 6int input = Console.Read(); 7if (input < 0 || input > 100) 8{ 9Console.WriteLine("Error"); 10} 11for (int i = 0; i <= 100; i++) 12{ 13if (i == input) 14{ 15//屏幕输出 16Console.WriteLine(i.ToString()); 17//弹出提示框 18MessageBox.Show(i.ToString()); 19//可能还有其他处理 20} 21} 22} 23 } 看到这个例子有什么感觉?循环的代码和处理的代码混在一起,可能还有未知的处理方式添加进来。耦合度非常高。再看看.net的处理方式吧 namespace Observer
{ //定义一个委托,这里定义了观察者方法的签名,就是一个协议吧 public delegate void NumberEventHandler(object sender, NumberEventArgs e); //要传递哪些参数到观察者?在这里定义,注意,要继承自EventArgs public class NumberEventArgs : EventArgs { public NumberEventArgs(int number) { _number = number; } private int _number; public int Number { get { return _number; } set { _number = value; } } } //观察者模式中的主题 public class Subject { //定义一个事件,就是委托的实例了 public event NumberEventHandler NumberReached; public void DoWithLoop(int number) { for (int i = 0; i <= 100; i++) { //触发事件的条件到了 if (i == number) { NumberEventArgs e = new NumberEventArgs(i); OnNumberReached(e); } } } //注意,这个方法定义为保护的,虚拟的,代表子类还可以进行覆盖,改变触发事件的行为 //甚至可以不触发事件 protected virtual void OnNumberReached(NumberEventArgs e) { //判断事件是否为null,也就是是否绑定了方法 if (NumberReached != null) NumberReached(this, e); } } public class MainProgram { public static void Main() { Console.WriteLine("Please Input a 0-100 Number:"); int input = Console.Read(); if (input < 0 || input > 100) { Console.WriteLine("Error"); } Subject s = new Subject(); //给事件绑定方法,静态的 s.NumberReached += new NumberEventHandler(msgbox_NumberReached); MainProgram mp = new MainProgram(); //给事件绑定方法,实例方法 s.NumberReached += new NumberEventHandler(mp.console_NumberReached); s.DoWithLoop(input); Console.Read(); } void console_NumberReached(object sender, NumberEventArgs e) { Console.WriteLine(e.Number.ToString()); } static void msgbox_NumberReached(object sender, NumberEventArgs e) { MessageBox.Show(e.Number.ToString()); } } } 虽然这个例子代码多多了,但是是值得的,事件触发的地方和处理的地方完全分离了,循环的位置不再需要知道有多少个方法正等着处理它 总结 经过几篇设计模式文章的介绍,也许有人会觉得设计模式一直在尝试解决几个问题:解藕,封装变化。设计模式一直在为可维护性,可扩展性,灵活性努力着。所以 学习设计模式并不是了解设计模式的原型,重要的是了解设计模式的场景和目的,这样你也可以在你自己的工作中总结出自己的设计模式。 有人说中国的数学教育是个错误,学习数学并不是学习那些定理公式,学习那些东西永远是跟在别人的后面,学习数学应该注重数学史的学习,循着数学发展的历史,了解前人是怎样分析问题,解决问题,学习前人的“渔”,并不仅仅是为了得到“鱼”。 本来上面的文章已经写定了,但今天看一MVP的文章又有点新的感触,觉得上面的总结又有点偏颇,学习模式重要的是她的精髓,但是“作为初学者即使知道所有 设计原则但是却不知道如何在项目应用”。是的,也许学习设计模式也要从“量变”引起“质变”。大量的应用,先不管是否是过度设计,到一定的时候也许就会得 到思想上的升华。 |
|
来自: lance library > 《设计模式》