接续...
[Architecture Pattern] Device Projection 模式 (上)
实做 :
范列下载 :
范列逻辑 :
下面图片是范例程序执行的结果。
主要的参与者有:
LightDevice.exe
-仿真远程设备的程序,采用TCP联机连接LightMaster。
-窗体上灯号数据的图像,可透过右侧灯号按钮做开关。
-窗体上灯号数据的图像,接受LightMaster传送来的指令做开关。
-每300ms会将灯号数据传送到LightMaster。
LightMaster.exe
-映像远程设备的程序,采用TCP联机聆听LightDevice连接。
-窗体上LightDevice数据列表,会映像LightDevice连接状态、灯号数据。
-选择窗体上一笔LightDevice数据后,可透过右侧灯号按钮对LightDevice传送指令做开关。
透过下面的图片说明,简单说明范例程序的互动流程。
模式封装 :
在建立范例之前,先将Tcp联机、范例用的通讯指令,套用Device Projection 模式来实做。
在实做的过程中发现Device Projection 模式,是可以精炼出可重用的模式封装。
所以在实做模式之前,先封装可重用的模式封装。让后续实做可以直接套用,缩短实做花费的时间。
模式封装的程序代码,主要是封装Device Projection 模式的运作逻辑。
相关细节可以下载范例档案参考,或是参考[Architecture Pattern] Device Projection 模式 (上)
模式实做 :
因为已经封装可重用的泛型,接下来的实做直接套用模式泛型。
主要的参与者有:
ILightDeviceSketch
-继承自IDeviceSketch的接口,提供IPEndPoint属性做为标识属性
-以接口形式出现,主要是要将通讯协议实做与整个模式做隔离。
namespace DeviceProjectionSample { public interface ILightDeviceSketch : IDeviceSketch { // Properties IPEndPoint IPEndPoint { get; } } }
ILightDeviceControl
-继承自IDeviceControl的接口,提供LightStatus属性、灯号开关方法
-以接口形式出现,主要是要将通讯协议实做与整个模式做隔离。
namespace DeviceProjectionSample { public interface ILightDeviceControl : IDeviceControl { // Properties bool LightStatus { get; } // Methods void OpenLight(); void CloseLight(); } }
LightDevice
-继承自Device的对象。
-实际提供给外部模块使用的对象。
-封装转接 ILightDeviceSketch、ILightDeviceControl所提供的属性跟方法。
-不支持额外IDeviceSketch当作属性来源,更新Device属性数据。
namespace DeviceProjectionSample { public class LightDevice : Device<ILightDeviceSketch, ILightDeviceControl> { // Constructor public LightDevice(ILightDeviceSketch deviceSketch, ILightDeviceControl deviceControl) : base(deviceSketch, deviceControl) { } // Properties public IPEndPoint IPEndPoint { get { return this.DeviceSketch.IPEndPoint; } } public bool LightStatus { get { return this.DeviceControl.LightStatus; } } // Methods protected override bool ImportProperty(ILightDeviceSketch deviceSketch) { return false; } public void OpenLight() { this.DeviceControl.OpenLight(); } public void CloseLight() { this.DeviceControl.CloseLight(); } } }
LightDeviceCollection
-继承自DeviceCollection的对象。
-模式内部Device对象实际存放的集合对象。
-除了提供模式内部存取之外,也开放给外部模块使用。
namespace DeviceProjectionSample { public class LightDeviceCollection : DeviceCollection<LightDevice, ILightDeviceSketch, ILightDeviceControl> { // Fields private readonly List<LightDevice> _deviceList = new List<LightDevice>(); // Methods protected override LightDevice GetDevice(ILightDeviceSketch deviceSketch) { #region Require if (deviceSketch == null) throw new ArgumentNullException(); #endregion foreach (LightDevice device in _deviceList) { if (device.IPEndPoint.ToString() == deviceSketch.IPEndPoint.ToString()) { return device; } } return null; } protected override void AddDevice(LightDevice device) { #region Require if (device == null) throw new ArgumentNullException(); #endregion _deviceList.Add(device); } protected override void RemoveDevice(LightDevice device) { #region Require if (device == null) throw new ArgumentNullException(); #endregion _deviceList.Remove(device); } public override IEnumerator<LightDevice> GetEnumerator() { return _deviceList.GetEnumerator(); } } }
LightDeviceManager
-继承自DeviceManager的对象。
-主要提供映像远程设备LightDevice。
namespace DeviceProjectionSample { public class LightDeviceManager : DeviceManager<LightDevice, ILightDeviceSketch, ILightDeviceControl> { // Constructor public LightDeviceManager(IDeviceFactory<LightDevice, ILightDeviceSketch, ILightDeviceControl> deviceFactory, IDeviceSketchExplorer<ILightDeviceSketch> deviceSketchExplorer) : this(new LightDeviceCollection(), deviceFactory, deviceSketchExplorer) { } private LightDeviceManager(LightDeviceCollection deviceCollection, IDeviceFactory<LightDevice, ILightDeviceSketch, ILightDeviceControl> deviceFactory, IDeviceSketchExplorer<ILightDeviceSketch> deviceSketchExplorer) : base(deviceCollection, deviceFactory, deviceSketchExplorer) { #region Require if (deviceCollection == null) throw new ArgumentNullException(); #endregion this.DeviceCollection = deviceCollection; } // Properties public LightDeviceCollection DeviceCollection { get; private set; } } }
MessagingClient
-封装TcpClient的对象。
-提供较方便的数据输入输出功能。
namespace DeviceProjectionSample.Concretion { public class MessagingClient : IDisposable { // Fields private readonly SynchronizationContext _syncContext = null; private readonly TcpClient _tcpClient = null; private readonly NetworkStream _networkStream = null; private readonly Thread _readThread = null; private bool _isDisposed = false; // Constructor public MessagingClient(SynchronizationContext syncContext, TcpClient tcpClient) { #region Require if (syncContext == null) throw new ArgumentNullException(); if (tcpClient == null) throw new ArgumentNullException(); #endregion _syncContext = syncContext; _tcpClient = tcpClient; _networkStream = tcpClient.GetStream(); _readThread = new Thread(this.ReadCommand); } public void Start() { // Start _readThread.Start(); } public void Dispose() { // Require if (_isDisposed == true) return; _isDisposed = true; // Dispose _networkStream.Dispose(); _tcpClient.Close(); _readThread.Join(); } // Properties public IPEndPoint IPEndPoint { get { return _tcpClient.Client.RemoteEndPoint as IPEndPoint; } } // Methods private void ReadCommand(object obj) { try { int commandCode = 0; do { commandCode = _networkStream.ReadByte(); if (commandCode >= 0) { this.OnCommandArrived((byte)commandCode); } } while (commandCode >= 0); } catch { } finally { this.OnDisconnected(this); } } public void SendCommand(byte commandCode) { if (_tcpClient.Connected == true) { _networkStream.WriteByte(commandCode); } } // Events internal event Action<MessagingClient> Disconnected; private void OnDisconnected(MessagingClient messagingClient) { #region Require if (messagingClient == null) throw new ArgumentNullException(); #endregion var handler = this.Disconnected; if (handler != null) { SendOrPostCallback handlerDelegate = delegate(object state) { handler = this.Disconnected; if (handler != null) { handler(messagingClient); } }; _syncContext.Post(handlerDelegate, null); } } public event Action<byte> CommandArrived; private void OnCommandArrived(byte commandCode) { var handler = this.CommandArrived; if (handler != null) { SendOrPostCallback handlerDelegate = delegate(object state) { handler = this.CommandArrived; if (handler != null) { handler(commandCode); } }; _syncContext.Post(handlerDelegate, null); } } } }
MessagingListener
-封装TcpListener的对象。
-提供较方便的数据TCP联机管理功能。
namespace DeviceProjectionSample.Concretion { public class MessagingListener : IDisposable { // Fields private readonly SynchronizationContext _syncContext = null; private readonly TcpListener _tcpListener = null; private readonly Thread _listenThread = null; private bool _isDisposed = false; private readonly object _syncRoot = new object(); private readonly List<MessagingClient> _messagingClientList = new List<MessagingClient>(); // Constructor public MessagingListener(SynchronizationContext syncContext, IPEndPoint localIPEndPoint) { #region Require if (syncContext == null) throw new ArgumentNullException(); if (localIPEndPoint == null) throw new ArgumentNullException(); #endregion _syncContext = syncContext; _tcpListener = new TcpListener(localIPEndPoint); _listenThread = new Thread(this.ListenClient); } public void Start() { // Start _tcpListener.Start(); _listenThread.Start(); } public void Dispose() { // Require if (_isDisposed == true) return; _isDisposed = true; // Dispose _tcpListener.Stop(); _listenThread.Join(); MessagingClient[] messagingClientArray = null; lock (_syncRoot) { messagingClientArray = _messagingClientList.ToArray(); _messagingClientList.Clear(); } foreach (MessagingClient messagingClient in messagingClientArray) { messagingClient.Dispose(); } } // Methods private void ListenClient(object obj) { try { while (true) { TcpClient tcpClient = _tcpListener.AcceptTcpClient(); if (tcpClient != null) { MessagingClient messagingClient = new MessagingClient(_syncContext, tcpClient); lock (_syncRoot) { messagingClient.Disconnected += new Action<MessagingClient>(MessagingClient_Disconnected); _messagingClientList.Add(messagingClient); } this.OnMessagingClientArrived(messagingClient); } } } catch (Exception ex) { if (_isDisposed == false) { Debug.Fail(ex.Message); } } finally { _tcpListener.Stop(); } } public MessagingClient GetMessagingClient(IPEndPoint ipEndPoint) { #region Require if (ipEndPoint == null) throw new ArgumentNullException(); #endregion lock (_syncRoot) { foreach (MessagingClient messagingClient in _messagingClientList) { if (messagingClient.IPEndPoint.ToString() == ipEndPoint.ToString()) { return messagingClient; } } return null; } } // Handlers private void MessagingClient_Disconnected(MessagingClient messagingClient) { #region Require if (messagingClient == null) throw new ArgumentNullException(); #endregion // Remove lock (_syncRoot) { if (_messagingClientList.Contains(messagingClient) == false) { return; } _messagingClientList.Remove(messagingClient); } // Event this.OnMessagingClientDeparted(messagingClient); } // Events public event Action<MessagingClient> MessagingClientArrived; private void OnMessagingClientArrived(MessagingClient messagingClient) { var handler = this.MessagingClientArrived; if (handler != null) { SendOrPostCallback handlerDelegate = delegate(object state) { handler = this.MessagingClientArrived; if (handler != null) { handler(messagingClient); } }; _syncContext.Post(handlerDelegate, null); } } public event Action<MessagingClient> MessagingClientDeparted; private void OnMessagingClientDeparted(MessagingClient messagingClient) { var handler = this.MessagingClientDeparted; if (handler != null) { SendOrPostCallback handlerDelegate = delegate(object state) { handler = this.MessagingClientDeparted; if (handler != null) { handler(messagingClient); } }; _syncContext.Post(handlerDelegate, null); } } } }
LightDeviceSketch
-ILightDeviceSketch的实做,主要是转接MessagingClient。
namespace DeviceProjectionSample.Concretion { public class LightDeviceSketch : ILightDeviceSketch { // Constructor public LightDeviceSketch(IPEndPoint ipEndPoint) { #region Require if (ipEndPoint == null) throw new ArgumentNullException(); #endregion this.IPEndPoint = ipEndPoint; } // Properties public IPEndPoint IPEndPoint { get; private set; } // Event public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { #region Require if (string.IsNullOrEmpty(propertyName) == true) throw new ArgumentNullException(); #endregion this.OnPropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } protected void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { #region Require if (sender == null) throw new ArgumentNullException(); if (e == null) throw new ArgumentNullException(); #endregion var handler = this.PropertyChanged; if (handler != null) { handler(sender, e); } } } }
LightDeviceControl
-ILightDeviceControl的实做,主要是转接MessagingClient。
-封装了与LightDevice之间沟通的通讯协议。
namespace DeviceProjectionSample.Concretion { public class LightDeviceControl : ILightDeviceControl { // Fields private readonly MessagingClient _messagingClient = null; private bool _lightStatus = false; // Constructor public LightDeviceControl(MessagingClient messagingClient) { #region Require if (messagingClient == null) throw new ArgumentNullException(); #endregion _messagingClient = messagingClient; _messagingClient.CommandArrived += new Action<byte>(MessagingClient_CommandArrived); } public void Start() { _messagingClient.Start(); } public void Dispose() { _messagingClient.Dispose(); } // Properties public bool LightStatus { get { return _lightStatus; } set { _lightStatus = value; this.OnPropertyChanged("LightStatus"); } } // Methods public void OpenLight() { _messagingClient.SendCommand(0x01); } public void CloseLight() { _messagingClient.SendCommand(0x00); } // Handler private void MessagingClient_CommandArrived(byte commandCode) { switch (commandCode) { case 0: if (this.LightStatus != false) { this.LightStatus = false; } break; case 1: if (this.LightStatus != true) { this.LightStatus = true; } break; } } // Event public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) { #region Require if (string.IsNullOrEmpty(propertyName) == true) throw new ArgumentNullException(); #endregion this.OnPropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } protected void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { #region Require if (sender == null) throw new ArgumentNullException(); if (e == null) throw new ArgumentNullException(); #endregion var handler = this.PropertyChanged; if (handler != null) { handler(sender, e); } } } }
LightDeviceSketchExplorer
-IDeviceSketchExplorer的实做,主要是转接MessagingListener。
namespace DeviceProjectionSample.Concretion { public class LightDeviceSketchExplorer : IDeviceSketchExplorer<ILightDeviceSketch> { // Fields private readonly MessagingListener _messagingListener = null; // Constructor public LightDeviceSketchExplorer(MessagingListener messagingListener) { #region Require if (messagingListener == null) throw new ArgumentNullException(); #endregion _messagingListener = messagingListener; _messagingListener.MessagingClientArrived += new Action<MessagingClient>(MessagingListener_MessagingClientArrived); _messagingListener.MessagingClientDeparted += new Action<MessagingClient>(MessagingListener_MessagingClientDeparted); } public void Start() { _messagingListener.Start(); } public void Dispose() { _messagingListener.Dispose(); } // Handlers private void MessagingListener_MessagingClientDeparted(MessagingClient messagingClient) { #region Require if (messagingClient == null) throw new ArgumentNullException(); #endregion this.OnDeviceSketchArrived(new LightDeviceSketch(messagingClient.IPEndPoint)); } private void MessagingListener_MessagingClientArrived(MessagingClient messagingClient) { #region Require if (messagingClient == null) throw new ArgumentNullException(); #endregion this.OnDeviceSketchDeparted(new LightDeviceSketch(messagingClient.IPEndPoint)); } // Events public event EventHandler<DeviceSketchNotifiedEventArgs<ILightDeviceSketch>> DeviceSketchArrived; private void OnDeviceSketchArrived(ILightDeviceSketch deviceSketch) { #region Require if (deviceSketch == null) throw new ArgumentNullException(); #endregion var handler = this.DeviceSketchArrived; if (handler != null) { handler(this, new DeviceSketchNotifiedEventArgs<ILightDeviceSketch>(deviceSketch)); } } public event EventHandler<DeviceSketchNotifiedEventArgs<ILightDeviceSketch>> DeviceSketchDeparted; private void OnDeviceSketchDeparted(ILightDeviceSketch deviceSketch) { #region Require if (deviceSketch == null) throw new ArgumentNullException(); #endregion var handler = this.DeviceSketchDeparted; if (handler != null) { handler(this, new DeviceSketchNotifiedEventArgs<ILightDeviceSketch>(deviceSketch)); } } } }
LightDeviceFactory
-IDeviceFactory的实做,主要是在生成LightDevice的时候,将相关的对象做关联。
namespace DeviceProjectionSample.Concretion { public class LightDeviceFactory : IDeviceFactory<LightDevice, ILightDeviceSketch, ILightDeviceControl> { // Fields private readonly MessagingListener _messagingListener = null; // Constructor public LightDeviceFactory(MessagingListener messagingListener) { #region Require if (messagingListener == null) throw new ArgumentNullException(); #endregion _messagingListener = messagingListener; } // Methods public LightDevice CreateDevice(ILightDeviceSketch deviceSketch) { #region Require if (deviceSketch == null) throw new ArgumentNullException(); #endregion MessagingClient messagingClient = _messagingListener.GetMessagingClient(deviceSketch.IPEndPoint); if (messagingClient == null) return null; return new LightDevice(deviceSketch, new LightDeviceControl(messagingClient)); } } }
范例实做 :
最后就只剩下的范例实做。
主要的参与者有:
LightMaster.exe
-提供在WinForm上如何使用实做出来的LightDeviceManager对象。
namespace LightMaster { public partial class Form1 : Form { // Fields private readonly IPEndPoint _localIPEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1234); private readonly LightDeviceManager _lightDeviceManager = null; // Constructor public Form1() { // Base InitializeComponent(); // LightDeviceManager MessagingListener messagingListener = new MessagingListener(SynchronizationContext.Current, _localIPEndPoint); LightDeviceFactory lightDeviceFactory = new LightDeviceFactory(messagingListener); LightDeviceSketchExplorer lightDeviceSketchExplorer = new LightDeviceSketchExplorer(messagingListener); _lightDeviceManager = new LightDeviceManager(lightDeviceFactory, lightDeviceSketchExplorer); // LightDeviceDataGridView BindingList<LightDevice> lightDeviceBindingList = new BindingList<LightDevice>(); _lightDeviceManager.DeviceArrived += delegate(object sender, DeviceNotifiedEventArgs<LightDevice> e) { lightDeviceBindingList.Add(e.Device); }; _lightDeviceManager.DeviceDeparted += delegate(object sender, DeviceNotifiedEventArgs<LightDevice> e) { lightDeviceBindingList.Remove(e.Device); }; this.LightDeviceBindingSource.DataSource = lightDeviceBindingList; this.LightDeviceDataGridView.Refresh(); } private void Form1_Load(object sender, EventArgs e) { // LightDeviceManager _lightDeviceManager.Start(); } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { // LightDeviceManager _lightDeviceManager.Dispose(); } // Handler private void OpenLightButton_Click(object sender, EventArgs e) { LightDevice device = this.LightDeviceBindingSource.Current as LightDevice; if (device != null) device.OpenLight(); } private void CloseLightButton_Click(object sender, EventArgs e) { LightDevice device = this.LightDeviceBindingSource.Current as LightDevice; if (device != null) device.CloseLight(); } } }
LightDevice.exe
-是仿真远程设备TCP联机处理、数据收发...等等行为的简单实做。
-相关细节可以下载范例档案参考。
后记 :
类似这样功能明确的Architecture Pattern,其实在大大小小的专案里都有。
花点心思整理纪录,让以后遇到类似需求的时候,能有文件来做沟通跟选择。
也希望让开发人员在需要实做相关功能时,能有一个参考的架构。