【转载】Unity C# の 观察者模式、.NET 框架中的委托和事件(三)

213 阅读5分钟

原文链接

Unity C# 多态 委托 事件 匿名委托 Lambda表达式 观察者模式 .NET 框架中的委托和事件 | Jason_c

原文写得很好,但是主题格式我不太喜欢,所以我对文章的格式进行了修整,方便自己日后阅读

正文

六、观察者模式

观察者模式:有时又被称为 发布(Publish )-订阅(Subscribe)模式模型-视图(View)模式源-收听者(Listener)模式从属者模式

观察者设计模式定义了对象间的一种 一对多 的依赖关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。

实现观察者模式有很多形式,比较直观的一种是使用一种 “注册——通知——撤销注册” 的形式:

  1. 观察者(Observer)将自己注册到被观察对象(Subject)中,被观察对象将观察者存放在一个容器(Container)里。
  2. 被观察对象发生了某种变化,从容器中得到所有注册过的观察者,将变化通知观察者。
  3. 观察者告诉被观察者要撤销观察,被观察者从容器中将观察者去除。

实现观察者模式的时候要 注意,观察者和被观察对象之间的互动关系不能体现成类之间的直接调用,否则就将使观察者和被观察对象之间紧密的耦合起来。无论是观察者 “观察” 观察对象,还是被观察者将自己的改变 “通知” 观察者,都不应该直接调用。

using System;

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 <= 100; i++)
            {
                temperature = i;
                if (temperature > 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();                          // 烧水,会自动调用注册过对象的方法
        }
    }
}
  • 运行结果 image.png

七、.NET 框架中的委托和事件

.NET Framework 的编码规范:

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

补充说明

  1. 委托声明原型中的 Object 类型的参数代表了 Subject,也就是监视对象。
  2. EventArgs 对象包含了 Observer 所感兴趣的数据。
using System;

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 <= 100; i++)
            {
                temperature = i;
                if (temperature > 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);
            }
        }
        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);
            }
        }

        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.BoilWater();                       //烧水,会自动调用注册过对象的方法
            }
        }
    }
}
  • 运行结果: image.png

文章参考自:C# 中的委托和事件(详解) 

附加观察者模板:

using System;
using System.Collections.Generic;

namespace Delegate
{
    /// <summary>
    /// 抽象主题类
    /// </summary>
    public abstract class Subject
    {
        private IList<Observer> observers = new List<Observer>();

        /// <summary>
        /// 增加观察者
        /// </summary>
        /// <param name="observer"></param>
        public void Attach(Observer observer)
        {
            observers.Add(observer);
        }

        /// <summary>
        /// 移除观察者
        /// </summary>
        /// <param name="observer"></param>
        public void Detach(Observer observer)
        {
            observers.Remove(observer);
        }

        /// <summary>
        /// 向观察者(们)发出通知
        /// </summary>
        public void Notify()
        {
            foreach (Observer o in observers)
            {
                o.Update();
            }
        }
    }

    /// <summary>
    /// 抽象观察者类,为所有具体观察者定义一个接口,在得到通知时更新自己
    /// </summary>
    public abstract class Observer
    {
        public abstract void Update();
    }

    /// <summary>
    /// 具体观察者或具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。
    /// </summary>
    public class ConcreteSubject : Subject
    {
        private string subjectState;

        /// <summary>
        /// 具体观察者的状态
        /// </summary>
        public string SubjectState
        {
            get { return subjectState; }
            set { subjectState = value; }
        }
    }

    /// <summary>
    /// 具体观察者,实现抽象观察者角色所要求的更新接口,已是本身状态与主题状态相协调
    /// </summary>
    public class ConcreteObserver : Observer
    {
        private string observerState;
        private string name;
        private ConcreteSubject subject;

        /// <summary>
        /// 具体观察者用一个具体主题来实现
        /// </summary>
        public ConcreteSubject Subject
        {
            get { return subject; }
            set { subject = value; }
        }

        public ConcreteObserver(ConcreteSubject subject, string name)
        {
            this.subject = subject;
            this.name = name;
        }

        /// <summary>
        /// 实现抽象观察者中的更新操作
        /// </summary>
        public override void Update()
        {
            observerState = subject.SubjectState;
            Console.WriteLine("The observer's state of {0} is {1}", name, observerState);
        }
    }
    
    class Program
    {
        static void Main()
        {
            // 具体主题角色通常用具体子类来实现
            ConcreteSubject subject = new ConcreteSubject ();

            subject.Attach (new ConcreteObserver (subject, "Observer A"));
            subject.Attach (new ConcreteObserver (subject, "Observer B"));
            subject.Attach (new ConcreteObserver (subject, "Observer C"));

            subject.SubjectState = "Ready";
            subject.Notify ();

            Console.Read ();
        }
    }
}
  • 运行结果: image.png