摘要:[Architecture] Device Projection (下)
接續...
[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,其實在大大小小的專案裡都有。
花點心思整理紀錄,讓以後遇到類似需求的時候,能有文件來做溝通跟選擇。
也希望讓開發人員在需要實做相關功能時,能有一個參考的架構。
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。