[IT168技术文档]作为行为模式中的一种,Observer模式与众不同的是它的关注重心不是对象的行为,而是两个或多个相互协作类之间的依赖关系。它之所以被称为行为模式,原因是它通过了某种行为来控制这种依赖关系,并产生消息通知进而达到修改被依赖的类的行为或状态的目的。GOF在《设计模式》一书中对Observer模式的意图有非常清晰地说明:“定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。”
Observer模式中最关键的一点是需要对观察者(Observer)角色进行抽象,唯有如此,方能解除观察者与被观察者之间的依赖关系。此外,被观察者即主体(Subject)角色,还需要维护一个集合对象,它是一个观察者对象的列表集合,在消息通知的时候,被观察者将遍历该集合内的观察者对象,并调用它们的相关方法。
在.NET Framework中,由于引入了委托与事件机制,因此,在实现Observer模式时,我们有了更多的选择。委托(delegate)是一个特殊的类,我们可以将其理解为函数指针,它能够指向符合委托定义的所有方法。因此它与接口有一定的相似性,且更具有广泛性,因为它可以接受静态方法,对方法名也没有限制,仅要求方法签名符合委托的定义。此外,委托链能够很好地管理多个观察者对象(即事件的订阅者),我们不需要建立专门的集合对观察者对象进行管理维护。因而,在Observer模式的实现上,利用委托与事件会是一个更佳的方案。
在本章中,我将选取CIMS系统中的一个功能需求进行分析,它是一个典型的Observer模式的实现。为了全面地展现Observer模式在.NET框架下的实现,我在给出了传统的Observer模式实现的基础上,又通过引入委托与事件机制实现了项目的需求,事实上,后一种方式,才是我们在项目中的真实实现。
1 需求分析——监控设备状态
在CIMS(Computer Integrated Manufacture System)中,有这样一个需求。服务端通过系统实时监听设备(Equipment)的状态,如果状态发生了改变,则通过.NET Remoting技术将消息发送到客户端。在客户端,设备以控件的形式放在容器控件GroupBox中,且控件的名字就是设备的名字。当客户端收到消息后,会在GroupBox中查找符合条件的控件,然后根据设备当前的状态,更新该控件的颜色。
根据需求分析,要求客户端系统具备如下功能。
— 客户端系统能够侦听到服务端传递来的消息,并获取设备的相关信息;
— 依据设备的信息,在相关窗体界面上查找符合条件的设备控件;
— 找到控件后,根据设备当前状态,修改控件的颜色。
由于服务端与客户端之间的消息传递不是本章所描述的重点,我们在识别对象时暂时将该功能点排除在外,那么识别出的对象就包括:
— 侦听器;
— 消息的通知者;
— 接收消息的窗体;
— 设备控件。
侦听器是一个Timer,每隔一定时间会去查询服务端发送来的消息。当消息到达后,会激发消息的通知者发送消息。添加了设备控件的窗体,则负责接收消息,然后执行相应的操作。
如果这些对象都存在于一个主程序窗体中,那么一切就变得异常简单了。我们只需要在Timer的Tick事件中实现这些逻辑就可以了。
public void timer_Tick(object sender, EventArgs e)
{
//接收服务端消息
//根据消息获取设备信息,并判断状态是否发生改变
//根据设备信息,查询GroupBox中符合条件的设备控件
//根据设备状态,修改设备控件的颜色
}
然而,现实的需求并非如此简单。事实上,在客户端可能具备多个需要显示设备控件的界面。也就是说,接收消息的窗体可能会有多个。此时,消息的通知者必须作为一个单独的对象存在,它是可以被重用的,同时,它也必须是可以扩展的。相似的,作为接收消息的窗体既然存在多个,自然也存在可能的扩展点。如此一来,设计就变得复杂了。
2 引入Observer模式
事实上,这是一个典型的Observer模式应用案例。所谓的消息通知者,就是Subject(主体),它会在设备状态发生变化时,通知Observer(观察者);接收消息的窗体,就是Observer(观察者)角色,待收到消息后,执行修改控件颜色的任务。
由于消息的通知者必须待设备状态发生变化时,方才发出消息到相关的窗体,因此,我们可以将Observer抽象为包含ChangeState方法的接口。
public interface IStateNotifier
{
void ChangeState(Equipment eqp);
}
其中,Equipment类以及状态枚举类型的定义如下。
public enum EQPState { Offline=0, Online }
public class Equipment
{
private EQPState m_state;
public
{
get {return m_state;}
set {m_state = value;}
}
//其他属性略
}
由于Subject的观察者可能不仅仅是一个,因此IStateNotifier接口还应包括添加和移除Observer对象的方法,修改如下。
public interface IStateNotifier
{
void ChangeState(Equipment eqp);
void AddStateChangeable(IStateChangeable stateChangeable);
void RemoveStateChangeable(IStateChangeable stateChangeable);
}
接口IStateChangeable就是Observer对象的抽象,它包含了FindAndChangeEQPState方法,负责查找设备控件,并修改控件的颜色。
public interface IStateChangeable
{
void FindAndChangeEQPState(Equipment eqp);
}
我们可以定义专门的类实现IStateChangeable接口,不过对于FindAndChangeEQPState方法而言,由于它的执行逻辑是在窗体的GroupBox中查找设备控件,所以,包含了这些设备控件的窗体本身就是一个Observer,可以直接实现IStateChangeable。例如,Operator的管理界面和Manufacture管理界面,它们的窗体类都必须履行Observer的职责。
public class OperatorForm:System.Windows.Forms.Form,IStateChangeable
{
public OperatorForm(IStateNotifier notifier)
{
notifier.AddStateChangeable(this);
}
public void FindAndChangeEQPState(Equipment eqp)
{
//实现略
}
}
public class ManufactureForm:System.Windows.Forms.Form,IStateChangeable
{
public ManufactureForm(IStateNotifier notifier)
{
notifier.AddStateChangeable(this);
}
public void FindAndChangeEQPState(Equipment eqp)
{
//实现略
}
}
由于在目前的需求中,消息的通知者仅有一个,因此,我定义了StateNotifier实现IStateNotifier接口。
public class StateNotifier:IStateNotifier
{
private List<IStateChangeable> m_list = new List<IStateChangeable>();
public void AddStateChangeable(IStateChangeable stateChangeable)
{
m_list.Add(stateChangeable);
}
public void RemoveStateChangeable(IStateChangeable stateChangeable)
{
m_list.Remove(stateChangeable);
}
public void ChangeState(Equipment eqp)
{
foreach (IStateChangeable changeable in m_list)
{
changeable.FindAndChangeEQPState(eqp);
}
}
}
整个结构的类图如图1所示。
注意:如果为了简化设计,在不考虑对消息的通知者扩展的情况下,接口IStateNotifier的定义就显得多余,此时可以去掉IStateNotifier,直接定义StateNotifier类。由于StateNotifier类是可以被实例化的,因此不能定义为抽象类,为保留可能的扩展性,应将方法ChangeState定义为虚方法。
图1 引入Observer模式后的类图
作为Subject角色的主窗体,当侦听器timer发现设备的状态发生改变后,就会调用StateNotifier类的相关方法。
public class MainForm:System.Windows.Forms.Form
{
private IStateNofifier m_notifier = new StateNotifier();
private Equipment m_eqp = new Equipment();
private Timer timer;
private IContainer components;
public MainForm()
{
OperatorForm opForm = new OperatorForm(m_notifier);
ManufactureForm maForm = new ManufactureForm(m_notifier);
opForm.Show();
maForm.Show();
InitializeComponent();
}
private void InitializeComponent()
{
//其他略
this.timer.Enabled = true;
this.timer.Interval = 1000;
this.timer.Tick += new System.EventHandler(this.timer_Tick);
}
private void timer_Tick(object sender, EventArgs e)
{
//获取服务端消息
//判断状态是否改变
if (IsChanged())
{
m_notifier.ChangeState(m_eqp);
}
}
private bool IsChanged()
{
return true;
}
}
此时,两个实现了IStateChangeable接口的窗体对象,在实例化时会将窗体类本身添加到m_notifier的List对象中。当侦听器timer监测到设备状态发生变化时,执行ChangeState方法,此时就将遍历m_notifier对象,执行OperatorForm和ManufactureForm窗体对象的FindAndChangeEQPState方法,实现改变控件颜色的目的。
通过引入Observer模式,既利于Subject对象的重用,又能保证Observer对象的扩展,同时还解除了Subject与Observer之间的耦合,可谓一举三得。