观察者模式
通过事件实现的观察者模式1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| using UnityEngine; using System; public class Publisher : MonoBehaviour { public static event Action OnEventPublished; void Update() { if (Input.GetKeyDown(KeyCode.Space)) { OnEventPublished?.Invoke(); } } } public class Subscriber : MonoBehaviour { private void OnEnable() { Publisher.OnEventPublished += HandleEvent; } private void OnDisable() { Publisher.OnEventPublished -= HandleEvent; } private void HandleEvent() { Debug.Log("Event received!"); } }
|
委托
委托是个类,分为Delegate自定义委托类型,Func有返回值的委托类型,Action无返回值的委托类型
Func和Action的底层原理便是用Delegate声明一个委托类型(有返回值和无返回值),并且通过泛型参数(最多十六个)来实现自定义参数类型和参数
其中,Func委托类型的最后一个参数为返回值
委托需要先定义后使用
1
| delegate void IntMethodInvoker(int x);
|
如上定义了一个委托InMethodInvoker,这个委托可以指向一个 int类型参数,返回值为void 的方法
Action委托 和 Func委托
Action委托引用了一个void返回类型的方法,T表示方法参数
1 2 3 4
| Action Action<in T> Action<in t1,in t2> Action<in t1,in t2,···,t16>
|
Func引用了一个带有一个返回值的方法,它可以传递0或者多到16个参数类型,和一个返回值类型
1 2 3
| Func<out TResult> Func<in t,out TResult> Function<in t1,in t2,···,in t16,out TResult>
|
多播委托
前面是用的委托都只包含一个方法调用,但是委托也可以包含多个方法,这种委托叫做多播委托。使用多播委托可以按照顺序调用多个方法,多播委托只能得到调用的最后一个方法结果,一般我们把多播委托的返回值类型声明为void。
多播委托包含一个逐个调用的委托集合,如果通过委托调用的其中一个方法抛出异常,整个迭代就会停止。
使用匿名方法给委托赋值
前面使用委托都是先定义一个方法,然后把方法给委托的实例。但还有另外一种使用委托的方式,不用去定义一个方法,直接使用匿名方法(lambda expression)
1 2 3 4 5 6 7 8 9 10
| Func<int,int,int> plus = delegate(int a, int b) { int temp = a+b; return temp; } int res = plus(34,34) Console.WriteLine(res);
Func<int, int, int> plus = (a, b) => { return a + b; };
|
事件
用event关键词修饰的字段,一种类型成员,有能力使一个类或者对象去通知其他类、对象们
事件是基于委托的,委托是事件的“底层基础”,事件是委托的“上层建筑”
委托类型定义了事件的有无返回值和参数类型,事件处理器必须和事件的有无返回值和参数类型一致,即双方都要遵守同一个约定(有无返回值和参数类型),我们把这叫做事件和事件处理器必须是匹配的
自定义事件
先声明该事件的委托类型,再声明事件
- 在声明委托类型的时候,如果这个委托,是为了声明某个事件而准备的委托,那么这个委托的名字,就要去使用:事件名+EventHandler的格式,由于委托是一种引用类型,所以事件名首字母要大写
- 在定义事件参数的时候,即在定义该事件的委托类型的参数的类型的时候,要遵循:类型名+EventArgs这个格式
事件声明示例1 2 3 4 5
| public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
public event OrderEventHandler OnOrder;
|
微软提供了一个EventHandler委托类型
其中用来传递事件数据的类EventArgs,凡是用来传递事件数据的类,都是从这个类派生出来的
让自定义的传递事件数据的类继承EventArgs,就可以作为参数传入EventHandler委托类型了
将Object类型的变量转换为Customer类型的变量,我们可以用as操作符
1 2 3
| public delegate void EventHandler(object? sender, EventArgs e);
Customer customer1 = _sender as Customer;
|
事件和委托的区别
- 事件其实是委托类型字段的包装器、限制器,限制外界对委托类型字段的访问。
- 外界只能通过“+=”和“-=”两个操作符对事件进行添加事件处理器和移除事件处理器的操作,并不能去赋值和触发事件。
- 事件是用来阻挡非法操作的“蒙版”,它绝对不是委托字段的本身
- 类似的情况有字段和属性,属性是字段的包装器。字段能做的,属性都能做;属性能做的,字段不一定都能做
总结:事件是用来“阉割”委托实例的,事件只能添加、删除事件处理器,不能赋值。外界只能用“+=”和“-=”去访问它,不能=,不能从外部触发事件,也就是说,事件包含了委托类型字段的所有功能,但只是对外部暴露了“+=”和“-=”操作符。
事件和委托的关系
委托类型规定了事件拥有者和事件响应者通知和接收的消息必须是同一类型的消息
约束了添加和移除事件时必须要使用与之匹配(同样类型)的事件处理器,即使用与之匹配(同样类型)的方法来处理响应这个事件
完整示例(顾客点单)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
| using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;
namespace EventDemo { public delegate void OrderEventHandler(Customer customer, OrderEventArgs e);
class EventExample { public static Customer customer = new Customer(); public static Customer customer2 = new Customer(); public static Waiter waiter = new Waiter(); static void Main() { customer.OnOrder += waiter.CalculateBill; customer2.OnOrder += waiter.CalculateBill; customer.Order("摩卡", 15, OrderEventArgs.CoffeeSizeEnum.Venti); customer.Order("拿铁", 20, OrderEventArgs.CoffeeSizeEnum.Tall); customer2.Order("卡布奇诺", 25, OrderEventArgs.CoffeeSizeEnum.Tall); customer.PayTheBill(); customer2.PayTheBill();
Console.Read(); } }
public class Customer { public event OrderEventHandler OnOrder;
public float Bill { get; set; } public void PayTheBill() { Console.WriteLine("I have to pay : " + Bill); }
public void Order(string coffeeName,float coffeePrice, OrderEventArgs.CoffeeSizeEnum coffeeSize) { if (OnOrder != null) { OrderEventArgs e = new OrderEventArgs(); e.CoffeeName = coffeeName; e.CoffeePrice = coffeePrice; e.CoffeeSize = coffeeSize; OnOrder(this, e); } } }
public class Waiter { public void CalculateBill(Customer customer, OrderEventArgs e) { float finalPrice = 0; switch (e.CoffeeSize) { case OrderEventArgs.CoffeeSizeEnum.Tall: finalPrice = e.CoffeePrice; break; case OrderEventArgs.CoffeeSizeEnum.Grand: finalPrice = e.CoffeePrice + 3; break; case OrderEventArgs.CoffeeSizeEnum.Venti: finalPrice = e.CoffeePrice + 6; break; } customer.Bill += finalPrice; } }
public class OrderEventArgs { public enum CoffeeSizeEnum { Tall,Grand,Venti} public CoffeeSizeEnum CoffeeSize { get; set; } public float CoffeePrice { get; set; } public string CoffeeName { get; set; } } }
|
Credits
https://blog.csdn.net/Hotgun2222/article/details/139901041