委托、事件与Observer设计模式

  • 一、委托 delegate 的用法
  • 1、将方法作为另一个方法的参数
  • 2、将方法绑定到委托
  • 二、事件 Event
  • 三、Observer观察者模式


一、委托 delegate 的用法

1、将方法作为另一个方法的参数

委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句和枚举定义,同时使得程序具有更好的可扩展性。

没看懂?没关系,看代码一目了然!

定义及使用方式案例:

public class ShowControl : MonoBehaviour
{
    public delegate void DelegateTool(string text);

    void Start()
    {
        ShowEvent showEvent = new ShowEvent();
        //调用
        ShowText(" AAA", showEvent.ShowA);
        ShowText(" BBB", showEvent.ShowB);
    }
    public void ShowText(string text, DelegateTool show)
    {
        show(text);
    }
}

public class ShowEvent : MonoBehaviour {
    public void ShowA(string text)
    {
        Debug.Log("ShowA" + text);
    }
    public void ShowB(string text)
    {
        Debug.Log("ShowB" + text);
    }
}

运行结果:

Unity 2022 LTS 鸿蒙_c#

2、将方法绑定到委托

使用委托可以将多个方法绑定到同一个委托变量,从而实现依次调用所有绑定的方法。

现在改写上面的Start方法:

void Start()
    {
        ShowEvent showEvent = new ShowEvent();
        DelegateTool delegateTool;
        delegateTool = showEvent.ShowA;
        delegateTool += showEvent.ShowB;
        //调用
        ShowText(" 哒哒哒", delegateTool);
    }

运行结果:

Unity 2022 LTS 鸿蒙_unity_02


注意这里,第一次用的“=”,是赋值的语法;第二次,用的是“+=”,是绑定的语法。如果第一次就使用“+=”,将出现“使用了未赋值的局部变量”的编译错误。

实际上,我们也可以绕过ShowText方法,通过委托来直接调用ShowA和ShowB,运行结果和上面一样:

void Start()
    {
        ShowEvent showEvent = new ShowEvent();
        DelegateTool delegateTool;
        delegateTool = showEvent.ShowA;
        delegateTool += showEvent.ShowB;
        //调用
        //ShowText(" 哒哒哒", delegateTool);
        delegateTool(" 哒哒哒");
    }

也可以再简化一下,效果一样:

void Start()
    {
        ShowEvent showEvent = new ShowEvent();

        //DelegateTool delegateTool;
        //delegateTool = showEvent.ShowA;
        //delegateTool += showEvent.ShowB;
        DelegateTool delegateTool = new DelegateTool(showEvent.ShowA);
        delegateTool += showEvent.ShowB;
        //调用
        ShowText(" 哒哒哒", delegateTool);
        //delegateTool(" 哒哒哒");
    }

既然给委托可以绑定一个方法,那么也应该有办法取消对方法的绑定,很容易想到,这个语法是“-=”:

void Start()
    {
        ShowEvent showEvent = new ShowEvent();
        DelegateTool delegateTool;
        delegateTool = showEvent.ShowA;
        delegateTool += showEvent.ShowB;
        ShowText(" 哒哒哒", delegateTool);
        
        //取消对ShowB的绑定
        delegateTool -= showEvent.ShowB;
        ShowText(" -----", delegateTool);
    }

运行结果:

Unity 2022 LTS 鸿蒙_c#_03

二、事件 Event

事件是对委托的封装,与之前委托变量DelegateTool的声明唯一的区别是多了一个event关键字。

public event DelegateTool delegateTool;

如果一个类里,你把一个委托声明为 public 了,那么外部就可以随意改变委托变量的值,包括清空委托变量等,这样的话就违背了面向对象思想的封装特性;但如果声明为 private ,那就失去了委托的意义(在外部不能给委托添加函数引用)。此时就需要事件了。

可以把事件看成是委托的实例,事件是对委托的封装,就像类的属性成员一样,事件封装了委托,这样,就可以把委托定义为 private 类型,在外部就可以通过与委托对应的事件来访问委托了,而事件受到限制符“+=”“-=”的影响,不会破坏封装的特性。

事件的本质就是委托。委托类型用 delegate 修饰,事件类型用 event 修饰。

另外:委托与事件的关系可以形象的用 字段与属性来进行类比,事件确实可以自定义add()、remove()方法,属性可以自定义get、set 。

上面的例子不足以介绍Event,跟着下面的Observer监视模式一起学习。

三、Observer观察者模式

Observer设计模式中主要包括如下两类对象:
1.Subject:监视对象,它往往包含着其他对象所感兴趣的内容。
2.Observer:监视者,它监视Subject,当Subject中的某件事发生的时候,会告知Observer,而Observer则会采取相应的行动。

假设我们有个高档的热水器,我们给它通上电,当水温超过95度的时候:
1、扬声器会开始发出语音,告诉你水的温度;
2、液晶屏也会改变水温的显示,来提示水已经快烧开了。
3、热水器由三部分组成:热水器、警报器、显示器

现在我们需要写个程序来模拟这个烧水的过程,我们将定义一个类来代表热水器,我们管它叫:Heater,它有代表水温的字段,叫做temperature;当然,还有必不可少的给水加热方法BoilWater(),一个发出语音警报的方法MakeAlert(),一个显示水温的方法,ShowMsg()。

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 -= alarm.MakeAlert;  //取消方法绑定需要用指定的实例化对象
      heater.BoilEvent += (new Alarm()).MakeAlert;  //给匿名对象注册方法
      heater.BoilEvent -= (new Alarm()).MakeAlert;  //未用指定的实例化对象,没有效果
      heater.BoilEvent += Display.ShowMsg;    //注册静态方法
 
      heater.BoilWater();  //烧水,会自动调用注册过对象的方法
    }
   }
 }

输出为:

Alarm:嘀嘀嘀,水已经 96 度了:
Display:水快烧开了,当前温度:96度。