故事
小雪是一个非常漂亮的女孩漂亮的女孩总是有很多的追求者而且追求者的队伍在不断的变动随时有人进入这个队伍也有人退出男孩们追求女孩时总是表现出%的关心当小雪私自游玩时总是不断收到追求者询问小雪位置变动的消息小雪也不胜其烦但小雪是如此的一个善良的女孩她总是打断自己正常的生活回复男孩们的消息而男孩们由于要不断的关心小雪的位置变化也弄的精疲力竭而且还影响正常的工作
在这样一个简单的故事场景中我们发现了什么?来看看小雪和男孩们的烦恼
男孩们必须不断的询问小雪的位置变化从而打断正常的工作
小雪也要不断的接受男孩们的询问有的时候小雪的位置并没有发生变化还是要不断的回复男孩们的询问也影响正常的工作
如果给各个男孩们回复问题的方式都不尽相同小雪还要知道不同的回复方式而且不断的有新的男孩们增加进来还不知道未来有什么新的回复方式
看到这么多烦恼我们创意无限的Nokia公司给小雪和男孩们提出了解决方案
Nokia公司荣誉出品了一款带有GPRS功能的手机该手机保存着一个订阅位置变化短信通知的电话列表当该手机检测到位置发生变化就会向这个订阅列表里的所有手机发送短信看到Nokia这个解决方案男孩们和小雪都应该松一口气他们各自都可以按照自己正常的生活习惯只有状态发生变化时候各自才会进行通信
观察者模式的解决方案
在上面Nokia的解决方案中就透露出观察者模式的思想观察者模式定义了对象之间一对多的依赖当这个对象的状态发生改变的时候多个对象会接受到通知有机会做出反馈在运行的时刻可以动态的添加和删除观察者
带着这个定义我们来看看尝试实现上面的观察者模式
首先在观察者模式中我们必须定义一个所有观察者都必须实现的接口这样被观察者向观察者发送消息的时候就可以使用统一的方式这也符合面相对象原则中的面向接口编程
//所有观察者都必须实现
public interface IBoy
{
//向男孩们显示小雪位置情况也就是向观察者发送消息观察者还可以对此做出反馈
void Show(string address);
}
using System;
//男孩A一个观察者
public class BoyA : IBoy
{
public void Show(string address)
{
//假设经过处理后为韩文的地址
ConsoleWriteLine(A:+address);
}
}
using System;
//男孩B又一个观察者
public class BoyB : IBoy
{
public void Show(string address)
{
//假设经过处理后为英语的地址
ConsoleWriteLine(B:+address);
}
}
下面看看小雪的实现也就是被观察者主要看看那个订阅的电话列表和怎样将消息通知给观察者
using System;
using SystemCollectionsGeneric;
public class GPRSMobile
{
//保存一个观察者列表
private List<IBoy> boys = null;
private string address = ;
public GPRSMobile()
{
boys = new List<IBoy>();
}
//添加观察者
public void AddBoy(IBoy b)
{
boysAdd(b);
}
public void RemoveBoy(IBoy b)
{
boysRemove(b);
}
//通知
private void Notify(string address)
{
for (int i = ; i < boysCount; i++)
{
boys[i]Show(address);
}
}
//位置发生变化
public void OnAddressChanaged(string newAddress)
{
//假设这里的地址是中文形式的
Notify(newAddress);
}
}
看到上面的代码例子我们可以给观察者模式的实现总结出这样几条规律第一被观察者必须保存着一个观察者列表第二所有的观察者必须实现一个统一的接口
那观察者模式到底有哪些好处呢?在上面的例子中我们可以看到被观察者仅仅依赖于一个实现了观察者接口的列表我们可以随时的向这个列表添加观察者而被观察者无须关注观察者是如何实现的当我们向观察者族中添加一个新的观察者被观察者无须作任何改变新的类型只要实现了观察者接口即可
在上面的描述中我们仿佛看到了面向接口编程无穷的力量面向接口编程使实现依赖于接口也就是实现依赖于抽象这样在被依赖对象发生改变的时候只要接口没有发生变化时依赖对象无须作任何变动
在现实中存在着很多观察者模式的实例比如在这个全民炒股的时代每个持有股票的人总是不断的关注着自己所买的股票的走势有人天天呆在交易大厅里看着屏幕上股票价格的走势有人在工作时间盯着电脑里股票软件为此很多公司采取各种各样的政策来制止这种行为这样不仅影响正常的上班且股票交易大厅常常人满为患如果有这样一个服务只要你订阅一个短信这个服务就会在你所关注的股票价格发生变动的时候短信通知你这样你就可以按照正常的顺序来做你的工作
中的观察者模式
中微软给我们带来一个更好的观察者模式的实现事件-委托
在Gof的观察者模式中(姑且称之为经典设计模式吧)观察者必须实现一个统一的接口里这个接口由委托的签名来保证了里的委托就是一个安全的函数指针(之所以说安全是与以前的C指针相比的C的函数指针并不包括函数的签名比如参数等东西所以可以传递一个并不是你期望的函数进去导致运行时出错由于这种错误在运行时发生很难检查出来)Ok现在以一的委托-事件的例子结束今天的观察者模式吧
描述这是一个控制台程序程序接收一个到之间整型的输入程序接收到输入后开始一个从到的循环当循环到你输入的数字的时候做一些处理我们将以两种方式来描述这个实例先用常规的方式然后采用委托-事件的方式
public class Program
{
static void Main(string[] args)
{
ConsoleWriteLine(Please Input a Number:);
int input = ConsoleRead();
if (input < || input > )
{
ConsoleWriteLine(Error);
}
for (int i = ; i <= ; i++)
{
if (i == input)
{
//屏幕输出
ConsoleWriteLine(iToString());
//弹出提示框
MessageBoxShow(iToString());
//可能还有其他处理
}
}
}
}
看到这个例子有什么感觉?
循环的代码和处理的代码混在一起可能还有未知的处理方式添加进来耦合度非常高再看的处理方式吧
namespace Observer
{
//定义一个委托这里定义了观察者方法的签名就是一个协议吧
public delegate void NumberEventHandler(object senderNumberEventArgs 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 = ; i <= ; 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()
{
ConsoleWriteLine(Please Input a Number:);
int input = ConsoleRead();
if (input < || input > )
{
ConsoleWriteLine(Error);
}
Subject s = new Subject();
//给事件绑定方法静态的
sNumberReached += new NumberEventHandler(msgbox_NumberReached);
MainProgram mp = new MainProgram();
//给事件绑定方法实例方法
sNumberReached += new NumberEventHandler(nsole_NumberReached);
sDoWithLoop(input);
ConsoleRead();
}
void console_NumberReached(object sender NumberEventArgs e)
{
ConsoleWriteLine(eNumberToString());
}
static void msgbox_NumberReached(object sender NumberEventArgs e)
{
MessageBoxShow(eNumberToString());
}
}
}
虽然这个例子代码多多了但是是值得的事件触发的地方和处理的地方完全分离了循环的位置不再需要知道有多少个方法正等着处理它
总结
经过几篇设计模式文章的介绍也许有人会觉得设计模式一直在尝试解决几个问题解藕封装变化设计模式一直在为可维护性可扩展性灵活性努力着所以学习设计模式并不是了解设计模式的原型重要的是了解设计模式的场景和目的这样你也可以在你自己的工作中总结出自己的设计模式
有人说中国的数学教育是个错误学习数学并不是学习那些定理公式学习那些东西永远是跟在别人的后面学习数学应该注重数学史的学习循着数学发展的历史了解前人是怎样分析问题解决问题学习前人的渔并不仅仅是为了得到鱼
本来上面的文章已经写定了但今天看一MVP的文章又有点新的感触觉得上面的总结又有点偏颇学习模式重要的是她的精髓但是作为初学者即使知道所有设计原则但是却不知道如何在项目应用是的也许学习设计模式也要从量变引起质变大量的应用先不管是否是过度设计到一定的时候也许就会得到思想上的升华