我们公司面试初级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。
好了,搞了这么久,前戏终于做完了。接下来可以开始说说观察者模式了。