观察者模式又叫做发布——订阅(Publish/Subscribe)模式。它的概念在我之前的博文中,也多次介绍过。今天,通过一个小Demo,模拟一下项目中使用观察者模式的基本结构。
概念回顾
首先,回顾一下观察者模式的概念。
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主体对象。这个主体对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
对于这些概念的东西,我们要结合实例来理解,这里我们联想大话设计模式中的例子:
公司中有看球的观察者A,有炒股票的观察者B;而老板和秘书两个通知者,就是两个主题。观察者A和观察者B同时监视着老板的反映。一旦老板有了新的指示,观察者A和观察者B以及所有的其他观察者,都会受到通知。
老板不关心具体谁会收到通知,他只管发通知;而且每个观察者之间也相对独立,他们只关注老板的反映。这就是观察者模式。
项目结构模拟
项目整体结构是这样的:
通过一些设备,实时检测各种类型的数据,新的数据以消息的形式通过Shuttle ESB传输。
Shuttle 服务器接收到消息,它会按照需求要求,对数据进行一定的加工处理,然后将消息注册到消息管理器(注意:这里的消息管理器,就是观察者中的主题)。而每一个显示终端就是一个观察者,当消息注册到消息管理器时,显示终端就会自动更新最新消息,然后在根据要求,进行显示。
这里,大家可以看出来:Shuttle不需要关心有多少个显示终端,它只管接收数据,经过处理后,它会发送给终端。这也就是观察者的好处,将发送和接收以Pub/Sub(发布/订阅)的形式,进行解耦合。
而且,显示终端之间是相互独立的,每个显示终端只是需要接收Shuttle的消息,显示终端彼此之间相互独立。
基本结构如下图:
代码实现
下面看代码实现:
1、Entity消息
每一种消息被定义为一个实体。如发送给终端的是风报警消息,那么风就定义为一种实体。这样的好处就是做到消息独立,应对变化。
风消息
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Entity { public class WindInfoEntity { public string id { get; set; } public string name{get;set;} } }
雨消息
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Entity { public class RainInfoEntity { public string id { get; set; } public string name { get; set; } } }
2、Subject接口
主题接口,没什么好说的。就是定义添加、移出、通知三个方法。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Collections; using Entity; using Observer; namespace Subject { /// <summary> /// 主题 /// </summary> public interface ISubject { /// <summary> /// 注册观察者 /// </summary> /// <param name="o"></param> void registerObserver(IObserver o); /// <summary> /// 移出观察者 /// </summary> /// <param name="o"></param> void removeObserver(IObserver o); /// <summary> /// 通知观察者 /// </summary> /// <param name="MessageType"></param> void notifyObservers(string MessageType); } }
3、主题的实现MessageDataSubject
实现了ISubject接口
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Subject; using System.Collections; using Entity; using Observer; namespace MessageDataSubject { public class MessageData : ISubject { public const string WindMessageType = "Wind"; public const string RainMessageType = "Rain"; //属性,用于保持数据 private ArrayList observers; IList<WindInfoEntity> WindMessagelist = new List<WindInfoEntity>(); IList<RainInfoEntity> RainMessagelist = new List<RainInfoEntity>(); public MessageData() { observers = new ArrayList(); } public void registerObserver(IObserver o) { observers.Add(o); } public void removeObserver(IObserver o) { int i = observers.IndexOf(0); if (i > 0) { observers.Remove(o); } } public void notifyObservers(string pMessageType) { if (pMessageType == WindMessageType) { foreach (IObserver o in observers) { o.Update(WindMessagelist); } } if (pMessageType == RainMessageType) { foreach (IObserver o in observers) { o.Update(RainMessagelist); } } } public void measurementsChanged(string pMessageType) { notifyObservers(pMessageType); } public void setMeasurements(IList<WindInfoEntity> pWindMessagelist) { this.WindMessagelist = pWindMessagelist; measurementsChanged(WindMessageType); } public void setMeasurements(IList<RainInfoEntity> pRainMessagelist) { this.RainMessagelist = pRainMessagelist; measurementsChanged(RainMessageType); } } }
4、CommunicationRouter
定义消息公共访问的方法,因为消息管理器是系统公共访问的,所以需要定义为静态的。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using MessageDataSubject; namespace CommunicationRouter { public class MessageManager { public static MessageData MessageTransfer { get; set; } } }
5、Globle
提供消息管理器的Get/Set方法
using System; using System.Collections.Generic; using System.Linq; using System.Text; using MessageDataSubject; using Subject; using CommunicationRouter; namespace Globle { public class GlobleProperty { public static MessageData MessageRegister { get { return MessageManager.MessageTransfer; } set { MessageManager.MessageTransfer = value; } } } }
6、Observer接口
观察者接口,定义观察者的消息更新方法。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Entity; namespace Observer { /// <summary> /// 抽象观察者 /// </summary> public interface IObserver { void Update(IList<WindInfoEntity> winds); void Update(IList<RainInfoEntity> rains); } }
7、EquipmentObserver观察者
具体观察者,这里需要将当前的窗体注册到主窗体的消息管理器。然后再实现消息的更新方法。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using Entity; using Globle; using MessageDataSubject; using Observer; using System.Windows.Threading; namespace EquipmentObserver { public partial class MainWindow : Window, IObserver { //定义基站消息管理器 public static MessageData baseStationMessageData = new MessageData(); public MainWindow() { InitializeComponent(); //将当前窗体注册到主窗体的消息管理器 GlobleProperty.MessageRegister.registerObserver(this); } private void Window_Loaded(object sender, RoutedEventArgs e) { } public void Update(IList<WindInfoEntity> StateWindlist) { if (StateWindlist == null || StateWindlist.Count <= 0) { return; } foreach (WindInfoEntity wind in StateWindlist) { Console.WriteLine("********风消息接收:" + new DateTime() + "====" + wind.name); } } public void Update(IList<RainInfoEntity> StateRainlist) { foreach (RainInfoEntity rain in StateRainlist) { Console.WriteLine("********雨消息接收:" + new DateTime() + "====" + rain.name); } } } }
8、客户端
这里是主项目,一般系统登陆后,都应该进入到这里。这里,我们模拟一个发数据的模拟器。每三秒钟发送一次数据。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using Entity; using System.Timers; using MessageDataSubject; namespace MainClient { public partial class MainWindow : Window { //定义消息管理器 static MessageData _MessageData = new MessageData(); public MainWindow() { InitializeComponent(); Globle.GlobleProperty.MessageRegister = _MessageData; } private void Window_Loaded(object sender, RoutedEventArgs e) { //定义一个计时器 System.Timers.Timer aTimer = new System.Timers.Timer(); //到达时间的时候执行事件GetAndSendMessages aTimer.Elapsed += new ElapsedEventHandler(callAllMessages); aTimer.Interval = 3000; //3秒 aTimer.AutoReset = true; //设置一致执行(true) aTimer.Enabled = true; //是否执行System.Timers.Timer.Elapsed事件 } private void callAllMessages(object source, ElapsedEventArgs e) { Console.WriteLine("************"+new DateTime() +"==MainClient发送消息"); //调用风推送 getAndSendWindMessage(); //调用雨推送 getAndSendRainMessage(); } /// <summary> /// 获取风数据,并推送给客户端 /// </summary> private void getAndSendWindMessage() { IList<WindInfoEntity> wind = new List<WindInfoEntity>(); WindInfoEntity w = new WindInfoEntity(); w.id = "w_1"; w.name = "w_风速一级"; wind.Add(w); w.id = "w_2"; w.name = "w_风速二级"; wind.Add(w); w.id = "w_3"; w.name = "w_风速三级"; wind.Add(w); w.id = "w_4"; w.name = "w_风速四级"; wind.Add(w); _MessageData.setMeasurements(wind); } /// <summary> /// 获取雨数据并推送给客户端 /// </summary> private void getAndSendRainMessage() { IList<RainInfoEntity> rain = new List<RainInfoEntity>(); RainInfoEntity r = new RainInfoEntity(); r.id = "r_1"; r.name = "r_风速一级"; rain.Add(r); r.id = "r_2"; r.name = "r_风速二级"; rain.Add(r); r.id = "r_3"; r.name = "r_风速三级"; rain.Add(r); r.id = "r_4"; r.name = "r_风速四级"; rain.Add(r); _MessageData.setMeasurements(rain); } private void button1_Click(object sender, RoutedEventArgs e) { EquipmentObserver.MainWindow mainwin = new EquipmentObserver.MainWindow(); mainwin.Show(); } } }
9、结果显示
这里的显示,涉及到了多线程问题,我们不做那么复杂,我们采用控制台输出(如上图)。
我们前端刷新传输数据,占用着主线程;而现在我们要在界面上将信息显示出来,所以我们还需要另一起新线程。这里我们主要是模拟项目中的观察者模式架构模型,这里就不深入阐述多线程了。(感兴趣的朋友,可以在下面留言)
这就是本文要介绍的所有内容。项目中基本的消息传输机制就是这样,项目就是利用观察者模式将发送者与接收者解耦合。
有一些比较灵活的地方。如每一个控件的位置大小,都是根据要求变化的,这时候,就是要在使用一重观察者模式,依照这种思路,再将控件附在窗体上。原理大家应该都懂得,感兴趣的朋友可以自己尝试一下。