[Architecture] Device Projection (下)

摘要:[Architecture] Device Projection (下)

接續...

[Architecture Pattern] Device Projection (上)

 

實做 :

 

範列下載 :

DeviceProjectionSample點此下載

 

範列邏輯 :

 

下面圖片是範例程式執行的結果。

 

主要的參與者有:

 

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,其實在大大小小的專案裡都有。
花點心思整理紀錄,讓以後遇到類似需求的時候,能有文件來做溝通跟選擇。
也希望讓開發人員在需要實做相關功能時,能有一個參考的架構。

期許自己
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。