首页 > 代码库 > 两种方式实现观察者模式

两种方式实现观察者模式

  什么是观察者模式?观察者模式包括观察者和被观察者。被观察者包含观察者感兴趣的字段或内容。当某件事发生的时候(通常是观察者感兴趣的内容),观察者会被自动告知,并且采取行动,调用处理方法。他们的交互模式是:被观察者提供注册和取消注册的方法,并保存对注册了事件的观察者的引用(通常是使用泛型集合)— 注册,保存观察者的引用 — 观察者包含一个事件发生时的处理方法 — 事件发生,被观察者调用一个方法,遍历集合,调用观察者的处理方法。

  这里先看看第一种,类似Flash里面使用addEventListener的方式监听某一事件,其他任意对象都可以通过发送事件来触发事件监听者在注册事件时绑定的方法,非常方便。我们可以定义一个string类型的事件名称,每一个string事件名对应一个集合,该集合用于保存所有注册了该事件的观察者的引用。

  先设计接口。

/// <summary>/// 观察者接口,实现注册和取消注册的方法,以及事件发生时调用的方法。/// </summary>public interface IObserver {    void UpdateEvent(string eventName);    void AddEventListener(string eventName);    void RemoveEventListener(string eventName);}
/// <summary>/// 被观察者接口,实现发送事件方法。/// </summary>public interface IObservable{    void DispatchEvent(string eventName);}

  接下来要定义一个全局的类,用来处理事件。

using System.Collections.Generic;/// <summary>///全局类,用于处理事件,在具体实现接口的方法时被调用 /// </summary>public class NotificationCenter {    //使用字典来存储事件。    private static Dictionary<string,List<IObserver>> notificationCenter = new Dictionary<string,List<IObserver>>();    //注册事件    public static void AddEvent(string eventName, IObserver observer)    {        if (!notificationCenter.ContainsKey (eventName)) {                        List<IObserver> list = new List<IObserver> ();                        notificationCenter.Add (eventName, list);                        list.Add (observer);                } else {            notificationCenter[eventName].Add(observer);        }    }    //取消注册    public static void RemoveEvent(string eventName, IObserver observer)    {        if (!notificationCenter.ContainsKey (eventName)) {            return;        } else {            notificationCenter[eventName].Remove(observer);        }    }    //发送事件    public static void DispatchEvent(string eventName){        if (!notificationCenter.ContainsKey (eventName)) {                        return;                    } else {            List<IObserver> list = notificationCenter[eventName];            foreach(IObserver observer in list)            {                observer.UpdateEvent(eventName);            }            }    }}

  好,下面可以写两个具体的类,一个做观察者,一个做被观察者。

using System.Collections;/// <summary>/// 观察者类,实现注册和取消注册的方法,以及事件处理方法。/// </summary>public class Cat : MonoBehaviour,IObserver{    //事件发生时调用的方法    public void UpdateEvent(string eventName)    {        Debug.Log (string.Format("发生了一件{0}事。",eventName));            }    //注册事件    public void AddEventListener(string eventName)    {        NotificationCenter.AddEvent (eventName, this);    }    //取消注册    public void RemoveEventListener(string eventName)    {        NotificationCenter.RemoveEvent (eventName, this);    }}
using UnityEngine;using System.Collections;/// <summary>/// 被观察者类,实现发送事件方法。/// </summary>public class Dog : MonoBehaviour,IObservable {    public void DispatchEvent(string eventName)    {        NotificationCenter.DispatchEvent (eventName);    }    IObserver cat;    void Start()    {        //cat = new Cat ();        cat =this.gameObject.GetComponent<Cat> ();        cat.AddEventListener ("a cat died");        for (int i = 0; i < 10; i++)         {            Debug.Log(i);            if(i == 8)            {                //当i=8时,发送事件,所有监听此事件的对象的事件处理方法都被调用。                this.DispatchEvent("a cat died");            }        }    }}

  其实为了代码的更好的重用,我们还可以设计两个基类,分别继承两个接口,这样我们只要继承该基类,就可以自动实现相关方法,不用每次自己写了。这个也是比较简单的一个写法,并没有考虑到超时和异常等问题。作为一个初学者,先有了相关概念,以后用到的时候可以再深入学习。下面看看如何使用委托来实现观察者模式。

  什么是委托?委托和事件有什么联系?我把委托理解成是一个方法类。所谓方法类,就是定义某一类方法的类。委托定义的方法具有相同的参数类型和返回值。比如:

public delegate void TestEventHandler(stirng name);

  TestEventHandler可以看做是一个方法的类型,TestEventHandler handle 和 string name差不多,不过name是一个字符串,handle则是一个方法,该方法必须有一个string的参数和void的返回值,也就是和声明委托时候的定义一样。既然它是一个类型,那就可以声明具体的变量。

public TestEventHandler handle;

  我们可以对handler进行赋值操作,让他等于某个方法,还可以对一个handle绑定多个方法,调用handle将依次调用这些方法。值得注意的是,假如定义了一个有返回值的委托类型,那么你最后得到的只是最后一个方法的返回值,前面的方法的返回值都被覆盖掉了。我们可以通过获取委托链表并依次调用这些方法的方式,来手动保存这些返回值。下面看事件。

public event TestEventHandler myEvent;

  那么,事件又是什么?事件是一种特殊的委托。首先,它是委托。其次,它特殊在哪儿呢?事件的绑定方法的方式就不一样,统一使用+=和-=来绑定方法,取消了普通委托第一次使用=赋值的约束。而且当你声明一个事件的时候,尽管使用了public修饰符,你也不能在类外面像调用委托那样直接调用它。你只能在内部调用事件。我们的某些不确定行为触发了事件,假如可以任意调用事件,那它就不是事件了。我们可以使用事件来实现一个简单的观察者模式,但是.net framework有了一个比较好的规范,用于定义委托的参数数量和参数类型,以及命名方式。

public delegate void TestEventHandler(object sender, TestEventArgs e);public event TestEventHandler testEvent;

  sender对象就是被观察者,TestEventArgs则定义了我们想传递的参数。把sender传递过去,有助于我们获取被观察的一些其它基本信息,我们可以声明自己的参数类,包括我们感兴趣的内容,然后让它继承自EventArgs,这样就可以符合.net framework的规范。遵守这个规范,可以使我们的程序更强大、灵活。下面使用标准规范实现一个简单的观察者模型。

   先写一个事件参数类。

using UnityEngine;using System.Collections;using System;/// <summary>/// 事件参数类,里面包含我们感兴趣的字段,以及一个构造方法。 /// </summary>public class TestEventArgs : EventArgs {    public readonly int age;    public TestEventArgs(int age)    {        this.age = age;    }}

  接下来定义一个Human类,Human对Pig的年龄比较感兴趣。

using UnityEngine;using System.Collections;/// <summary>/// 观察者类,Human对Pig的age比较感兴趣。 /// </summary>public class Human : MonoBehaviour {    //触发事件时执行的方法    public void KillPig(object sender, TestEventArgs e)    {        Debug.Log (string.Format("小猪猪现在{0}岁了,该杀了吃了。",e.age));    }}

  最后定义被观察者类Pig。当pig到一定年龄,要通知Human执行相应方法。

using UnityEngine;using System.Collections;/// <summary>/// 被观察者,在这里声明委托和事件。 /// </summary>public class Pig : MonoBehaviour {    //标准规范的委托声明    public delegate void TestEventHandler(object sender, TestEventArgs e);    //声明事件    public event TestEventHandler testEvent;    public int age;//假设age字段是某些人感兴趣的    //做了某件事,触发事件之后,调用这个方法    protected virtual void OnAgeChanged(TestEventArgs e)    {        if (testEvent != null) {            testEvent(this,e);                }    }    void DoSomething()    {        for (int i = 0; i < 10; i++) {            age = i;            if(age<8)            {                Debug.Log(string.Format("小猪猪{0}岁了",age));            }            if(age ==8)//假设小猪8岁的时候,要宰了吃了            {                TestEventArgs e = new TestEventArgs(age);                OnAgeChanged(e);            }        }    }    void Start(){        Human human = GetComponent<Human>();        testEvent += human.KillPig;        DoSomething ();    }}

 

两种方式实现观察者模式