我们公司面试初级C#的笔试题有一道题目:“请用代码实现:狗叫,主人被惊醒,猫跑了”。
这是一道很典型的可以使用观察者模式来解答的题目,可惜来做题的伙伴经常没有答上,今天我就从这道小题目开始,由浅到深讲讲观察者模式。
但是在讲观察者模式之前我们先要了解委托和事件。
委托和事件
委托(delegate)本质上是函数指针(在js里是函数变量的引用,js里并不需要声明delegate)。通俗来理解就是把一件事交给别人去做。
在代码里,“事情”就是functioin或method。delegate的作用就是把functioin或method装起来,不在new的地方执行,而在别的地方执行。
delegate需要把要执行的method注册进去,即告诉被委托人你要委托给他的事。
知道了委托,我们可以先试试水,用它来实现最开始说的笔试题的第一版。这里大家先不用去想为什么要这样做。
首先定义三位演员和他们要做的事:
public class Dog
{
public void Bark()
{
Console.WriteLine("狗:汪汪汪");
}
}
public class Master
{
public void WakeUp()
{
Console.WriteLine("主人:醒来");
}
}
public class Cat
{
public void Run()
{
Console.WriteLine("猫:跑了");
}
}
其次,我们把三位演员new出来,并把他们要做的事放入委托,最后通过“中介人”来执行委托:
public delegate void TheTestDelegate();
//定义一个中间人来执行委托,需要接收一个委托参数
private static void Agent(TheTestDelegate doSomething) {
doSomething();
}
static void Main(string[] args)
{
Dog dog = new Dog();
Master master = new Master();
Cat cat = new Cat();
TheTestDelegate testDelegate;
//注册要委托的事
testDelegate = dog.Bark;
testDelegate += master.WakeUp;
testDelegate += cat.Run;
//执行委托
Agent(testDelegate);
}
这样一来,中间人就把我们刚刚委托给他的事都给办了
这样写的好处是显而易见的。委托将Main和各位演员的操作隔离了。每次执行委托时,我并不需要知道各位演员即将要做什么骚操作(比如狗突然喵喵叫)。从而达到了解耦的效果。
知道了什么是委托和委托怎么用之后,接下来我们看看什么是事件:
事件(event)是一种封装了的委托,他做了什么封装,为什么要这么封装呢?我们来继续研究上面的例子。
在上面的代码里,委托被定义成了公共的变量。这样就意味着谁都可以修改,那显然不行。
所以我们要让中介人自己管理他的委托合同。只有雇佣(new)他的人,才可以委托他干活。而作为发布人的我们仅需要发布委托和触发执行事件。
首先我们把Agent单独建一个class,但是我们要怎么让中介人自己管理委托呢?即使把delegate移到Agent里,如果将delegate声明为public,那照样谁都可以赋值。若声明为private,则谁都委托不了。
这时我们可以试试看用事件:
//定义委托
public delegate void TheTestDelegate();
public class Agent
{
//声明事件
public event TheTestDelegate theEvent;
public void DoDelegate()
{
theEvent();
}
}
static void Main(string[] args)
{
Agent agent = new Agent();
//注册要委托的事
agent.theEvent += new Dog().Bark;//注意,这里第一次不需要也不能用=号了
agent.theEvent += new Master().WakeUp;
agent.theEvent += new Cat().Run;
//触发事件
agent.DoDelegate();
}
大家可能觉得有疑问:这不还是用了public吗?
其实不然,为什么这里把method注册进事件不需要也不能使用“=”号了呢?
其实声明事件类似于把委托声明成了属性 (Property)。我们的+=和-=相当于访问了set访问器,而真正的delegate其实被声明成了private。
好了,搞了这么久,前戏终于做完了。接下来可以开始说说观察者模式了。