接续...
[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,其实在大大小小的专案里都有。
花点心思整理纪录,让以后遇到类似需求的时候,能有文件来做沟通跟选择。
也希望让开发人员在需要实做相关功能时,能有一个参考的架构。



