您当前的位置: 首页 >  Peter_Gao_ c#

C# Observer设计模式 ——委托与事件的案例分析

Peter_Gao_ 发布时间:2020-04-25 16:26:01 ,浏览量:6

事件是委托的部分应用。

用于两个对象之间消息的发布和自动监听。

实现步骤:

一、 创建发布者类

  1. 声明事件委托(类)
  2. 声明事件成员(委托类实例)
  3. 发布事件(方法)

二、 创建订阅者(类)

  1. 订阅事件(方法)
  2. 处理事件(方法)

三、创建事件触发(类)

    触发事件(方法)

Demo1: 当狗狗发出有小偷的报警事件消息,主人听到了消息后抓住了小偷,警察听到消息后跑了过来。

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;

 

    //一、事件发布者


    public class Dog : MonoBehaviour
    {
        //1.声明事件委托类;
        public delegate void AlarmEventHandler(object sender, EventArgs e);

        //2.声明事件成员;   
        public event AlarmEventHandler Alarm;

        //3.发布事件消息;
        public void OnAlarm()
        {
            if (this.Alarm != null)
            {
                Console.WriteLine("\n狗报警: 有小偷进来了,汪汪~~~~~~~");
                // 调用事件
                this.Alarm(this, new EventArgs());   //发出警报
            }
        }
    }

 

    //二、事件订阅者


    class Host
    {

        // 4.事件(链)订阅
        public Host(Dog dog)
        {
            dog.Alarm += new Dog.AlarmEventHandler(this.HostHandleAlarm);
        }

        // 5.事件处理器。订阅者接收到消息后的后续操作。(是一个委托类型)
        void HostHandleAlarm(object sender, EventArgs e)
        {
            Console.WriteLine("主人监听到报警后抓住了小偷!");
        }
    }

 

    // 事件订阅者


    class Police
    {
        // 4.订阅事件(链)
        public Police(Dog dog)
        {
            dog.Alarm += new Dog.AlarmEventHandler(this.PoliceHandleAlarm);
        }

        // 5. 事件处理器。 订阅者接收到消息后进行的后续操作。
        void PoliceHandleAlarm(object sender, EventArgs e)
        {
            Console.WriteLine("警察监听到报警后跑了过来!");
        }
    }

 

    // 三、 事件触发(类)

 

    class Program
    {
        static void Main(string[] args)
        {
            Dog dog = new Dog();
            Host host = new Host(dog);

            //当前时间,从2008年12月31日23:59:50开始计时
            DateTime now = new DateTime(2015, 12, 31, 23, 59, 50);
            DateTime midnight = new DateTime(2016, 1, 1, 0, 0, 0);

            //等待午夜的到来
            Console.WriteLine("时间一秒一秒地流逝... ");
            while (now < midnight)
            {
                Console.WriteLine("当前时间: " + now);
                System.Threading.Thread.Sleep(1000);    //程序暂停一秒
                now = now.AddSeconds(1);                //时间增加一秒
            }

            //午夜零点小偷到达,看门狗引发Alarm事件
            Console.WriteLine("\n月黑风高的午夜: " + now);
            Console.WriteLine("小偷悄悄地摸进了主人的屋内... ");

            //6.触发事件
            dog.OnAlarm();
            Console.ReadLine();
        }
    }

 

 

Demo2: 当锅炉的温度和压力超出预设的阀值则自动引发报警日志事件,并显示在控制台同时写入日志。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;

namespace BoilerEventAppl
{

    // boiler 类
    class Boiler
    {
        private int temp;
        private int pressure;
        public Boiler(int t, int p)
        {
            temp = t;
            pressure = p;
        }

        public int getTemp()
        {
            return temp;
        }
        public int getPressure()
        {
            return pressure;
        }
    }

 


    // 一、事件发布者


    class DelegateBoilerEventPublisher
    {
        // 1. 声明事件委托类
        public delegate void BoilerLogHandler(string status);

        // 2. 声明事件成员
        public event BoilerLogHandler BoilerEventLog;

        // 3. 发布事件,一旦温度和压力超出设定的阀值就广播发布需要进行记录日志的告警信息
        public void LogProcess()
        {
            string remarks = "O. K";
            Boiler b = new Boiler(100, 12);
            int t = b.getTemp();
            int p = b.getPressure();
            if (t > 150 || t < 80 || p < 12 || p > 15)
            {
                remarks = "警报:Need Maintenance";
            }
            OnBoilerEventLog("Logging Info:\n");
            OnBoilerEventLog("Temparature " + t + "\nPressure: " + p);
            OnBoilerEventLog("\nMessage: " + remarks);
        }

        // 调用事件
        protected void OnBoilerEventLog(string message)
        {
            if (BoilerEventLog != null)
            {
                BoilerEventLog(message);
            }
        }
    }


    // 二、事件订阅者


    public class RecordBoilerInfoScriber
    {
        static void Main(string[] args)
        {
            BoilerInfoLogger filelog = new BoilerInfoLogger("e:\\boiler.txt");
            DelegateBoilerEventPublisher boilerEventPublisher = new DelegateBoilerEventPublisher();

            // 4.订阅事件
            boilerEventPublisher.BoilerEventLog += new // 注册锅炉日志事件的处理程序
            DelegateBoilerEventPublisher.BoilerLogHandler(Logger); // 

            boilerEventPublisher.BoilerEventLog += new
            DelegateBoilerEventPublisher.BoilerLogHandler(filelog.Logger);


            // 6.触发事件 ( 三、 事件触发(类))
            boilerEventPublisher.LogProcess();
            Console.ReadLine();
            filelog.Close();
        }//end of main

 

        // 5. 事件处理器。 订阅者收到消息后的后续操作
        static void Logger(string info)
        {
            Console.WriteLine(info);  // 控制台显示日志内容
        }//end of Logger

       

    }//end of RecordBoilerInfo

    //  5. 事件处理器。该类把日志内容写入日志文件
    class BoilerInfoLogger
    {
        FileStream fs;
        StreamWriter sw;
        public BoilerInfoLogger(string filename)
        {
            fs = new FileStream(filename, FileMode.Append, FileAccess.Write);
            sw = new StreamWriter(fs);
        }
        public void Logger(string info)
        {
            sw.WriteLine(info);
        }
        public void Close()
        {
            sw.Close();
            fs.Close();
        }
    }

}

 

Demo3: 类似上面热水器案例

using System;
using System.Collections.Generic;
using System.Text;

namespace Delegate {
    // 发布者:热水器
    public class Heater {
       private int temperature;
       public delegate void BoilHandler(int param);   //声明委托
       public event BoilHandler BoilEvent;        //声明事件

       // 烧水
       public void BoilWater() {
           for (int i = 0; i
95) {
                  if (BoilEvent != null) { //如果有对象注册
                      BoilEvent(temperature);  //发布事件消息(自动调用所有注册对象的方法,引发订阅者收到事件消息后进行后续操作)
                  }
              }
           }
       }
    }

    // 订阅者:警报器
    public class Alarm {
       public void MakeAlert(int param) { // 处理事件,接收到发布者发布的事件消息后采取的后续操作
           Console.WriteLine("Alarm:嘀嘀嘀,水已经 {0} 度了:", param);
       }
    }

    // 订阅者:显示器
    public class Display {
       public static void ShowMsg(int param) { //处理事件 (静态方法)
           Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", param);
       }
    }
   
    class Program {
       static void Main() {
           Heater heater = new Heater();
           Alarm alarm = new Alarm();


           // 订阅事件(链)
           heater.BoilEvent += alarm.MakeAlert;    //注册方法
           heater.BoilEvent += (new Alarm()).MakeAlert;   //给匿名对象注册方法
           heater.BoilEvent += Display.ShowMsg;       //注册静态方法

           // 触发事件
           heater.BoilWater();   //烧水,会自动调用注册过对象的方法
       }
    }
}


输出为:
Alarm:嘀嘀嘀,水已经 96 度了:
Alarm:嘀嘀嘀,水已经 96 度了:
Display:水快烧开了,当前温度:96度。
// 省略...

 

 

Demo4: 对上面Demo3重构,使其符合.Net Framework 的规范:

 

 .Net Framework的编码规范:

  • 委托类型的名称都应该以EventHandler结束。
  • 委托的原型定义:有一个void返回值,并接受两个输入参数:一个Object 类型,一个 EventArgs类型(或继承自EventArgs)。
  • 事件的命名为 委托去掉 EventHandler之后剩余的部分。
  • 继承自EventArgs的类型应该以EventArgs结尾。

再做一下说明:

  1. 委托声明原型中的Object类型的参数代表了Subject,也就是监视对象,在本例中是 Heater(热水器)。回调函数(比如Alarm的MakeAlert)可以通过它访问触发事件的对象(Heater)。
  2. EventArgs 对象包含了Observer所感兴趣的数据,在本例中是temperature。

上面这些其实不仅仅是为了编码规范而已,这样也使得程序有更大的灵活性。比如说,如果我们不光想获得热水器的温度,还想在Observer端(警报器或者显示器)方法中获得它的生产日期、型号、价格,那么委托和方法的声明都会变得很麻烦,而如果我们将热水器的引用传给警报器的方法,就可以在方法中直接访问热水器了。

 

using System;
using System.Collections.Generic;
using System.Text;

namespace Delegate {
    // 热水器
    public class Heater {
       private int temperature;
       public string type = "RealFire 001";       // 添加型号作为演示
       public string area = "China Xian";         // 添加产地作为演示
       
       public delegate void BoiledEventHandler(Object sender, BoiledEventArgs e);//声明委托
       public event BoiledEventHandler Boiled; //声明事件

       // 定义BoiledEventArgs类,传递给Observer所感兴趣的信息
       public class BoiledEventArgs : EventArgs {
           public readonly int temperature;
           public BoiledEventArgs(int temperature) {
              this.temperature = temperature;
           }
       }

       // 可以供继承自 Heater 的类重写,以便继承类拒绝其他对象对它的监视
       protected virtual void OnBoiled(BoiledEventArgs e) {
           if (Boiled != null) { // 如果有对象注册
              Boiled(this, e);  // 发布事件消息,调用所有注册对象的方法
           }
       }
      
       // 烧水。
       public void BoilWater() {
           for (int i = 0; i
95) {
                  //建立BoiledEventArgs 对象。
                  BoiledEventArgs e = new BoiledEventArgs(temperature);
                  OnBoiled(e);  // 调用 OnBolied方法
              }
           }
       }
    }

    // 警报器
    public class Alarm {
       public void MakeAlert(Object sender, Heater.BoiledEventArgs e) {
           Heater heater = (Heater)sender;     //这里是不是很熟悉呢?
           //访问 sender 中的公共字段
           Console.WriteLine("Alarm:{0} - {1}: ", heater.area, heater.type);
           Console.WriteLine("Alarm: 嘀嘀嘀,水已经 {0} 度了:", e.temperature);
           Console.WriteLine();
       }
    }

    // 显示器
    public class Display {
       public static void ShowMsg(Object sender, Heater.BoiledEventArgs e) {   //静态方法
           Heater heater = (Heater)sender;
           Console.WriteLine("Display:{0} - {1}: ", heater.area, heater.type);
           Console.WriteLine("Display:水快烧开了,当前温度:{0}度。", e.temperature);
           Console.WriteLine();
       }
    }

    // 日志记录器

     public class Loger{

        public void RecordLog(Object sender, Heater.BoiledEventArgs e){ 

            Heater heater = (Heater)sender;

            Console.WriteLine("Loger: 开始记录到日志..);

             Console.WriteLine();

          }

       }



    class Program {
       static void Main() {
           Heater heater = new Heater();
           Alarm alarm = new Alarm();


           // 订阅事件
           heater.Boiled += alarm.MakeAlert;   //注册方法
           heater.Boiled += (new Alarm()).MakeAlert;      //给匿名对象注册方法
           heater.Boiled += new Heater.BoiledEventHandler(alarm.MakeAlert);    //也可以这么注册
           heater.Boiled += Display.ShowMsg;       //注册静态方法

           heater.Boiled += Loger.RecordLog; // 

 

           // 触发事件
           heater.BoilWater();   //烧水,会自动调用注册过对象的方法
       }
    }
}

输出为:
Alarm:China Xian - RealFire 001:
Alarm: 嘀嘀嘀,水已经 96 度了:
Alarm:China Xian - RealFire 001:
Alarm: 嘀嘀嘀,水已经 96 度了:
Alarm:China Xian - RealFire 001:
Alarm: 嘀嘀嘀,水已经 96 度了:
Display:China Xian - RealFire 001:
Display:水快烧开了,当前温度:96度。
// 省略 ...

 

 

Demo5: 计时器初始化:检测网络如果离线,计时器开始正向计时,如果网络在线,计时器开始获取网络时间并开始倒计时。

           如果网络一直离线或在线,则计时器继续初始计时状态。

一、 创建发布者类:  NetStatusPublisher

  1. 声明事件委托(类) delegate  NetChangedEventHandler
  2. 声明事件成员(委托类实例)event NetChangedEventHandler NetChanged
  3. 发布事件(方法)OnNetChanged {

                                     if (NetChanged != null)

                                     {  this.NetChanged();  } }

                          上次的网络状态信息 lastNetStatus

                         轮询网络状态 StartCoroutine CheckNet {

                               协程 while { 

                                       问询一次网络状态

                                        yield return CheckNetStatus()

                                         bool isConnectedNow = CheckNetStatus()

                                         可能会有2种网络发生改变的情况,分别发布事件消息:

                                          1. 离线变为在线

                                          if (isConnectedNow==true && (lastNetStatus == false))

                                                 OnNetChanged(status1)

                                         2. 在线变为离线

                                          else if (isConnectedNow==false && (lastNetStatus == true))

                                                 OnNetChanged(status2) 

                                         3. 一直离线或一直在线则不做处理,不发消息

 

                                        yield return waitForSecond(2.0f)

                                        等待2秒后进行下一次问询

                                         }

                          }

二、 创建订阅者(类): TimerScriber

  1. 订阅事件(方法):
  2. 处理事件(方法): 收到消息后,做不同处理。

三、创建事件触发(类)

    触发事件(方法) : start 开始协程死循环

 

 

 

 

 

关注
打赏
查看更多评论

Peter_Gao_

暂无认证

  • 6浏览

    0关注

    534博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录