设备状态监控之观察者模式

有很长一段时间没有写博客了,因为生活中遭遇了一些不愉快的事情,让我无法静下心来写一篇技术博客,反而更想表达一些生活的感悟,呵呵,眼看着2019就快要结束了,圈子里也看到了一些大佬们的年终总结的帖子,我想我也该写一篇关于我的2019,但是我并不想写在技术论坛里,所以,这里还是纯粹的谈论学习,那么,回到今天的主题内容,关于设备状态监控的。

我接到的需求就是在一个窗体里,实时显示设备的状态,每一种状态用对应的图标显示,其实核心的需求就是这么简单。那么,拿到这样的需求,脑海里首先想到的就是使用观察者模式来实现,为什么是观察者模式呢?简单的回答的话,那就是经验,经验告诉我观察者模式很契合上述需求的实现,专业一点的话,那么需要我们先了解何为观察者模式?

何为观察者模式?

观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己

举个例子:微信公众号,相信大家都关注过,这个关注就是订阅的过程,那么,这个公众号有新的文章发布的时候,你就会收到这个公众号推送的消息,这个推送就是发布的过程,这就是观察者模式最经典的应用。

回到我们的设备监控,每一个图标控件就是观察者,设备状态发生改变的时候,通知我们的观察者,观察者根据设备状态再来改变图标显示,这就达到我们的目的了。那么,已经有了整体的思路,我们就开始编码吧!

观察者模式核心代码

/// <summary>
    /// 设备监控接口
    /// </summary>
    public interface IDevicesMonitor
    {
        /// <summary>
        /// 添加观察者
        /// </summary>
        /// <param name="observer">观察者</param>
        void RegisterObserver(IObserver observer);

        /// <summary>
        /// 移除观察者
        /// </summary>
        /// <param name="observer">观察者</param>
        void RemoveObserver(IObserver observer);

        /// <summary>
        /// 通知观察者
        /// </summary>
        void NotifyObserver();
    }
/// <summary>
    /// 设备观察者接口
    /// </summary>
    public interface IObserver
    {
        /// <summary>
        /// 更新设备状态
        /// </summary>
        /// <param name="deviceStatus"></param>
        void Update(DeviceInfo deviceInfo);
    }

观察者模式,实现上述两个接口,就完成了,且看代码:设备监控类中,使用了设计模式中的单例模式

/// <summary>
    /// 设备监控类
    /// </summary>
    public class DevicesMonitor : IDevicesMonitor
    {
        /// <summary>
        /// 保存所有的观察者
        /// </summary>
        private ConcurrentBag<IObserver> _observers;

        /// <summary>
        /// 设备信息
        /// </summary>
        public DeviceInfo DeviceInfo { private get; set; }

        /// <summary>
        /// 单例模式
        /// </summary>
        private static DevicesMonitor singleton = new DevicesMonitor();
        private static object lockObj = new object();

        /// <summary>
        /// 监控类 单例
        /// </summary>
        /// <returns></returns>
        public static DevicesMonitor GetSingleton()
        {
            if (singleton == null)
            {
                lock (lockObj)
                {
                    if (singleton == null)
                    {
                        singleton = new DevicesMonitor();
                    }
                }
            }
            return singleton;
        }

        private DevicesMonitor()
        {
            _observers = new ConcurrentBag<IObserver>();
        }

        public void NotifyObserver()
        {
            foreach (var observer in _observers)
            {
                observer.Update(DeviceInfo);
            }
        }

        /// <summary>
        /// 添加观察者
        /// </summary>
        /// <param name="observer"></param>
        public void RegisterObserver(IObserver observer)
        {
            _observers.Add(observer);
        }

        /// <summary>
        /// 移除观察者
        /// </summary>
        /// <param name="observer"></param>
        public void RemoveObserver(IObserver observer)
        {
            _observers.TryTake(out observer);
        }

        /// <summary>
        /// 设置设备状态
        /// </summary>
        /// <param name="deviceInfo"></param>
        public void SetStatus(DeviceInfo deviceInfo)
        {
            this.DeviceInfo = deviceInfo;
            NotifyObserver();
        }
    }

观察者,使用自定义控件,继承Panel和IOberver接口

public partial class ucDevice : Panel, IObserver
    {
        public ucDevice(DeviceInfo deviceInfo)
        {
            InitializeComponent();
            this.DeviceInfo = deviceInfo;
            InitDeviceStatus(deviceInfo);
            _status = deviceInfo.Status;
        }

        int _xPos;
        int _yPos;
        bool _moveFlag;

        enumDeviceStatus _status = enumDeviceStatus.UnKnow;

        /// <summary>
        /// 删除电子地图中的设备的时候 触发的事件
        /// </summary>
        public event Action<ucDevice> OnDeleteDevice;

        /// <summary>
        /// 设备状态发生改变的时候 触发的事件
        /// </summary>
        public event Action<DeviceInfo, enumDeviceStatus> OnStatusChanged;

        public DeviceInfo DeviceInfo { get; set; }


        public void Update(DeviceInfo deviceInfo)
        {
            if (this.DeviceInfo.Id != deviceInfo.Id) return;
            if (_status != deviceInfo.Status)
            {
                this.DeviceInfo.Status = deviceInfo.Status;
                InitDeviceStatus(this.DeviceInfo);
                if (OnStatusChanged != null)
                {
                    OnStatusChanged(this.DeviceInfo, _status);
                }
                _status = deviceInfo.Status;
            }
        }

        private void InitDeviceStatus(DeviceInfo deviceInfo)
        {
            StringBuilder stringBuilder = new StringBuilder();
            if (!string.IsNullOrEmpty(deviceInfo.DeviceName))
            {
                this.lblMsg.Text = deviceInfo.DeviceName;
                stringBuilder.Append("设备名称:").Append(deviceInfo.DeviceName).Append(Environment.NewLine);
            }
            if (!string.IsNullOrEmpty(deviceInfo.IpAddr))
            { stringBuilder.Append("IP地址:").Append(deviceInfo.IpAddr).Append(Environment.NewLine); }
            if (!string.IsNullOrEmpty(deviceInfo.Mac))
            { stringBuilder.Append("Mac地址:").Append(deviceInfo.Mac).Append(Environment.NewLine); }


            this.lblMsg.ForeColor = Color.Red;
            this.toolTip1.ToolTipTitle = "Warning";
            this.toolTip1.ToolTipIcon = ToolTipIcon.Warning;
            switch (deviceInfo.Status)
            {
                case enumDeviceStatus.Normal:
                    this.lblMsg.ForeColor = Color.Blue;
                    this.toolTip1.ToolTipTitle = "Normal";
                    this.toolTip1.ToolTipIcon = ToolTipIcon.Info;
                    stringBuilder.Append("设备状态:正常").Append(Environment.NewLine);
                    break;
                case enumDeviceStatus.Warning:
                    stringBuilder.Append("设备状态:线路1和线路2都离线").Append(Environment.NewLine);
                    break;
                case enumDeviceStatus.Warning_1:
                    stringBuilder.Append("设备状态:线路1离线").Append(Environment.NewLine);
                    break;
                case enumDeviceStatus.Warning_2:
                    stringBuilder.Append("设备状态:线路2离线").Append(Environment.NewLine);
                    break;
            }
            
            this.toolTip1.SetToolTip(picDevice, stringBuilder.ToString());

            string fileName = deviceInfo.DeviceType.ToString() + "_" + deviceInfo.Status.ToString();
            string imagePath = EnvironmentInfo.ImagesPath.Find(x => x.Contains(fileName.ToLower()));

            if (File.Exists(imagePath))
            {
                this.picDevice.Image = Image.FromFile(imagePath);

            }
            else
            {
                this.picDevice.Image = Image.FromFile(EnvironmentInfo.ImagesPath.Find(x => x.Contains(enumDeviceStatus.UnKnow.ToString().ToLower())));
            }
        }

        private void UcDeviceContainter_MouseMove(object sender, MouseEventArgs e)
        {
            if (_moveFlag)
            {
                this.Left += Convert.ToInt16(e.X - _xPos);//设置x坐标.
                this.Top += Convert.ToInt16(e.Y - _yPos);//设置y坐标.
            }
        }

        private void UcDeviceContainter_MouseDown(object sender, MouseEventArgs e)
        {
            _moveFlag = true;//已经按下.
            _xPos = e.X;//当前x坐标.
            _yPos = e.Y;//当前y坐标.
        }

        private void UcDeviceContainter_MouseUp(object sender, MouseEventArgs e)
        {
            _moveFlag = false;
        }

        private void ToolStripMenuItemDelete_Click(object sender, EventArgs e)
        {
            if (OnDeleteDevice != null)
            {
                OnDeleteDevice(this);
            }
        }
    }

最后就是通过对设备状态的监控来完成设备状态的刷新,这里只是一个模拟的效果,直接从数据库中获取设备的状态,然后来刷新设备状态,具体的项目,根据自己的业务来决定

public class ClientProxy
    {
        private readonly string _connString = "";

        /// <summary>
        /// 后台监控线程
        /// </summary>
        BackgroundWorker _backgroundWorker = new BackgroundWorker();

        /// <summary>
        /// 休眠时间 单位:秒
        /// </summary>
        public int SleepTimes { private get; set; } = 10;

        public ClientProxy(string connString)
        {
            _connString = connString;

            InitData();
            EnvironmentInfo.InitProxy();//必须先初始化
            DoWork();
        }

        /// <summary>
        /// 初始化数据
        /// </summary>
        /// <returns></returns>
        public List<DeviceInfo> InitData()
        {
            try
            {
                List<DeviceInfo> deviceInfos = new List<DeviceInfo>();
                string sql = "select * from deviceinfo";
                DataTable dt = MySqlHelper.ExecuteDataset(_connString, sql).Tables[0];//从数据库中获取数据
                foreach (DataRow dr in dt.Rows)
                {
                    enumDeviceType deviceType = enumDeviceType.Router;
                    switch (dr["DeviceType"].ToString())
                    {
                        case "0":
                            deviceType = enumDeviceType.Server;
                            break;
                        case "1":
                            deviceType = enumDeviceType.Router;
                            break;
                        case "2":
                            deviceType = enumDeviceType.ATM;
                            break;
                    }
                    deviceInfos.Add(new DeviceInfo()//转换成 所需要的实体列表
                    {
                        Id = int.Parse(dr["Id"].ToString()),
                        DeviceName = dr["DeviceName"].ToString(),
                        IpAddr = dr["IpAddr"].ToString(),
                        ParentId = int.Parse(dr["ParentId"].ToString()),
                        Status = enumDeviceStatus.Normal,
                        DeviceType = deviceType
                    });
                }
                return deviceInfos;
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 开始监控设备状态
        /// </summary>
        private void DoWork()
        {
            _backgroundWorker.WorkerReportsProgress = true;
            _backgroundWorker.DoWork += _backgroundWorker_DoWork;
            _backgroundWorker.ProgressChanged += _backgroundWorker_ProgressChanged;
            if (!_backgroundWorker.IsBusy)
            {
                _backgroundWorker.RunWorkerAsync();
            }
        }

        private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            while (true)
            {
                string sql = "select * from deviceinfo d,monitor m where d.Id=m.DeviceId";
                DataTable dt = MySqlHelper.ExecuteDataset(_connString, sql).Tables[0];
                foreach (DataRow dr in dt.Rows)
                {
                    enumDeviceType deviceType = enumDeviceType.Router;
                    switch (dr["DeviceType"].ToString())
                    {
                        case "0":
                            deviceType = enumDeviceType.Server;
                            break;
                        case "1":
                            deviceType = enumDeviceType.Router;
                            break;
                        case "2":
                            deviceType = enumDeviceType.ATM;
                            break;
                    }
                    DeviceInfo deviceInfo = new DeviceInfo()
                    {
                        Id = int.Parse(dr["Id"].ToString()),
                        DeviceType = deviceType
                    };

                    string netWorkStr = dr["Network1"].ToString() + dr["Network2"].ToString();
                    switch (netWorkStr)
                    {
                        case "00":
                            deviceInfo.Status = enumDeviceStatus.Warning;
                            break;
                        case "01":
                            deviceInfo.Status = enumDeviceStatus.Warning_1;
                            break;
                        case "10":
                            deviceInfo.Status = enumDeviceStatus.Warning_2;
                            break;
                        case "11":
                            deviceInfo.Status = enumDeviceStatus.Normal;
                            break;
                        default:
                            deviceInfo.Status = enumDeviceStatus.Normal;
                            break;
                    }

                    _backgroundWorker.ReportProgress(1, deviceInfo);

                }

                System.Threading.Thread.Sleep(SleepTimes * 1000);
            }
        }

        private void _backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (e.ProgressPercentage == 1)
            {
                DeviceInfo deviceInfo = e.UserState as DeviceInfo;
                if (deviceInfo != null)
                {
                    DevicesMonitor.GetSingleton().SetStatus(deviceInfo);
                }
            }
        }
    }

脚本:

CREATE TABLE `deviceinfo` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `IpAddr` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL,
  `Mac` varchar(17) COLLATE utf8_unicode_ci DEFAULT NULL,
  `DeviceName` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  `ParentId` int(11) DEFAULT NULL,
  `DeviceType` int(11) NOT NULL,
  PRIMARY KEY (`Id`),
  KEY `index_IpAddr` (`IpAddr`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `monitor` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `DeviceId` int(11) DEFAULT NULL,
  `Network1` int(11) DEFAULT NULL,
  `Network2` int(11) DEFAULT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

源代码:https://e.coding.net/dwBurning/DeviceMonitor.githttps://e.coding.net/dwBurning/DeviceMonitor.githttps://e.coding.net/dwBurning/DeviceMonitor.githttps://e.coding.net/dwBurning/DeviceMonitor.git

最后的效果,上个图:

原文地址:https://www.cnblogs.com/dwBurning/p/DeviceMonitorObserver.html