博客园  :: 首页  :: 联系 :: 管理

观察者模式

Posted on 2013-08-16 16:11  雪庭  阅读(2590)  评论(0编辑  收藏  举报
观察者模式

观察者模式

1 观察者模式概述

观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图 (Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察 者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个 主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

观察者模式被称为是模式中的皇后,而且java jdk也对它做了实现,可见该设计模式的重 要位置。在图形化设计的软件中,为了实现视图和事件处理的分离,大多都采用了 Observer模式,比如java的Swing,Flex的ActionScript等。在现实的应用系统中也有好多 应用,比如像当当网、京东商城一类的电子商务网站,如果你对某件商品比较关注,可以 放到收藏架,那么当该商品降价时,系统给您发送手机短信或邮件。这就是观察者模式的 一个典型应用,商品是被观察者,有的叫主体;关注该商品的客户就是观察者。下面的一 个事例将模拟这个应用。

1.1 jdk中观察者模式实现

java jdk中定义了:Observable对象(被观察者)和Observer接口(观察者).它们的关系可总结如下:

  1. Observable和Observer是一对多的关系,也就是说一旦Observable状态变化,它就要负责 通知所有和它有关系的Observer,然后做相应的改变
  2. Observable不会主动去通知各个具体的Observer其状态发生了变化,而是提供一个注册 接口供Observer使用,任何一个Observer如果想要被通知,则可以使用这个接口 来注册
  3. 在Observable中有一个集合和一个状态控制开关,所有注册了通知的Observer会被保 存在这个集合中.这个控制开关就是用来控制Observable是否发生了变化,一旦发生了变 化,就通知所有的Observer更新状态

我们先看一下jdk是如何实现的。被观察者的抽象类java.util.Observable

package java.util;

public class Observable {
  private boolean changed = false;
  private Vector obs;

  /**
   * 创建被观察者时就创建一个它持有的观察者列表,注意,这个列表是需要同步的。
   */
  public Observable() {
    obs = new Vector();
  }

  /**
   * 添加观察者到观察者列表中去
   */
  public synchronized void addObserver(Observer o) {

    if (o == null)
      throw new NullPointerException();
    if (!obs.contains(o)) {
      obs.addElement(o);
    }
  }

  /**
   * 删除一个观察者
   */ 
  public synchronized void deleteObserver(Observer o) {
    obs.removeElement(o);
  }

  public void notifyObservers() {
    notifyObservers(null);
  }

  /**
   * 通知操作,即被观察者发生变化,通知对应的观察者进行事先设定的操作,这个方法接受一个参数,这个参数一直传到观察者里,以供观察者使用
   */ 
  public void notifyObservers(Object arg) {
    Object[] arrLocal;
    synchronized (this) {
      if (!changed)
        return;
      arrLocal = obs.toArray();
      clearChanged();
    }
    for (int i = arrLocal.length-1; i>=0; i--)
      ((Observer)arrLocal[i]).update(this, arg);
  }

  public synchronized void deleteObservers() {
    obs.removeAllElements();
  }

  protected synchronized void setChanged() {
    changed = true;
  }

  protected synchronized void clearChanged() {
    changed = false;
  }


  public synchronized boolean hasChanged() {
     return changed;
  }

  public synchronized int countObservers() {
    return obs.size();
  }
}

当我们自己的被观察者继承这个Observable类是,我们就自动的获取到被观察者的一切条 件了。很方便是不是,这也是为什么sun要把Observable放到java.util包中的原因,就是 为了方便开发者。

下面我们再看一下观察者的接口java.util.Observer

package java.util;

public interface Observer {
  void update(Observable o, Object arg);
}

接口中就只有一个方法,update,方法中有两个参数,Observable和一个object,第一个 参数就是被观察的对象,而第二个参数就得看业务需求了,需要什么就传进去什么。我们 自己的观察者类必须实现这个方法,这样在被观察者调用notifyObservers操作时被观察者 所持有的所有观察者都会执行update操作了,见下图:

2 java中观察者模式应用

2.1 swing事件驱动编程

Swing 框架以事件侦听器的形式广泛利用了观察者模式(也称为发布-订阅模式)。 Swing 组件作为用户交互的目标,在用户与它们交互的时候触发事件;数据模型类在数据 发生变化时触发事件。用这种方式使用观察者,可以让控制器与模型分离,让模型与视图 分离,从而简化 GUI 应用程序的开发。

AWT 和 Swing 组件(例如 JButton 或 JTable)使用观察者模式消除了 GUI 事件生成与 它们在指定应用程序中的语义之间的耦合。类似地,Swing 的模型类,例如 TableModel 和 TreeModel,也使用观察者消除数据模型表示 与视图生成之间的耦合,从而支持相同 数据的多个独立的视图。Swing 定义了 Event 和 EventListener 对象层次结构;可以生 成事件的组件,例如 JButton(可视组件) 或 TableModel(数据模型),提供了 addXxxListener() 和 removeXxxListener() 方法,用于侦听器的登记和取消登记。这些 类负责决定什么时候它们需要触发事件,什么时候确实触发事件,以及什么时候调用所有 登记的侦听器。

为了支持侦听器,对象需要维护一个已登记的侦听器列表,提供侦听器登记和取消登记的 手段,并在适当的事件发生时调用每个侦听器。使用和支持侦听器很容易(不仅仅在 GUI 应用程序中),但是在登记接口的两边(它们是支持侦听器的组件和登记侦听器的组 件)都应当避免一些缺陷。

2.2 监听器和监听器模式

监听器(Listener)是观察者模式的一种实现,监听器模式也就是观察者模式的一种。监听 器模式是对某种共有操作的监控。当此操作执行时对此操作作相应处理。包含的元素:

  • 要监控的事件定义Event
  • 监控该事件的监听器Listener
  • 要监控的事件操作Action
  • 监控者

这里举一个例子,说明监听模式的一般实现:

  • 首先要定义事件,监听器处理哪些类型的事件,也就是用什么样的事件来触发监听器, 事件的类型很多,这里可以定义一个事件接口来抽象所有事件类型:
/** 
 * 事件接口,其中事件的类型定义了三种,创建、删除、更新 
 */  
public interface MyEvent {  

    public static final String createEvent = "CREATE_EVENT";  
    public static final String deleteEvent = "DELETE_EVENT";  
    public static final String updateEvent = "UPDATE_EVENT";  

    public String getEvent();  

}  
  • 给出一个监听器的接口
/** 
 * 定义监听器,该监听器只监听MyEvent类型的事件 
 */  
public interface MyListener {  
    public void handle(MyEvent myEvent);  
}  
  • 监听器的实现,该实现根据事件类型的不同做不同的处理:
public class MyListenerImpl implements MyListener {  
    public void handle(MyEvent myEvent) {  
        if(myEvent.getEvent().equals("CREATE_EVENT")){  
            System.out.println("myListener get a create event!");  
        }  
        else if(myEvent.getEvent().equals("DELETE_EVENT")){  
             ...   
        }  
         ...   
    }  
}  
  • 有了监听器的实现,肯定需要一个事件的实现,不过事件的实现类可以是专指某个类型 的pojo,也可以是一个事件类型在使用时被设置的类,下面的实现是第一种,直接定义 了一个create事件的实现类:
public class MyCreateEventImpl implements MyEvent {  
    private String type="";  
    public MyCreateEventImpl(){  
        this.type = MyEvent.createEvent;  
    }  
    public String getEvent() {  
        return this.type;  
    }  
}  
  • 基础工作都做好后,就可以在想添加监听的类中实施监听功能了:
public class MyCreateAction {  
    //引入监听器  
    private MyListener myListener;  
    //引入事件,用来传给Listener进行处理  
    private static MyEvent myEvent;  
    public void setListener(MyListener myListener){  
        this.myListener = myListener;  
    }  
    private void handleListener(MyEvent myEvent){  
        //触发监听  
        this.myListener.handle(myEvent);  
    }  
    public void execute(){  
        //设置事件的类型为create  
        myEvent = new MyCreateEventImpl();  
        System.out.println("create start!");  
        this.handleListener(myEvent);  
        System.out.println("create end!");  
    }  

    //调用被监听的类,测试监听效果  
    public static void main(String[] args) {  
        MyCreateAction action = new MyCreateAction();  
        MyListenerImpl myListener = new MyListenerImpl();  
        //设置监听器的实现  
        action.setListener(myListener);  
        action.execute();  
    }  
}  
  • 输出的结果为:
create start!

myListener get a create event!

create end!

2.3 Servlet中的listener

我们在web.xml中配置listener的时候就是把一个被观察者放入的观察者的观察对象队列 中,当被观察者触发了注册事件时观察者作出相应的反应。在jsp/servlet中具体的实现 是在web.xml中注册Listener,由Container在特定事件发生时呼叫特定的实现Listener的 类。

总体上说servlet中有主要有3类事件既:Servlet上下文事件、会话事件与请求事件总共 有8个listener接口,我们在web.xml中注册时对应上自己对相应接口的实现类即可, Servlet中的Listener和Event,在JSP 2.0/Servlet 2.4中,共有八个Listener接口,六 个Event类别:

  1. ServletContextListener接口
    • [接口方法] contextInitialized()与 contextDestroyed()
    • [接收事件] ServletContextEvent
    • [触发场景] 在Container加载Web应用程序时(例如启动 Container之后),会呼叫 contextInitialized(),而当容器移除Web应用程序时,会呼叫contextDestroyed ()方法
  2. ServletContextAttributeListener
    • [接口方法] attributeAdded()、 attributeReplaced()、attributeRemoved()
    • [接收事件] ServletContextAttributeEvent
    • [触发场景] 若有对象加入为application(ServletContext)对象的属性,则会呼叫 attributeAdded(),同理在置换属性与移除属性时,会分别呼叫 attributeReplaced()、attributeRemoved()。
  3. HttpSessionListener
    • [接口方法] sessionCreated()与sessionDestroyed ()
    • [接收事件] HttpSessionEvent
    • [触发场景] 在session (HttpSession)对象建立或被消灭时,会分别呼叫这两个方 法。
  4. HttpSessionAttributeListener
    • [接口方法] attributeAdded()、 attributeReplaced()、attributeRemoved()
    • [接收事件] HttpSessionBindingEvent
    • [触发场景] 若有对象加入为session(HttpSession)对象的属性,则会呼叫 attributeAdded(),同理在置换属性与移除属性时,会分别呼叫 attributeReplaced()、 attributeRemoved()。
  5. HttpSessionActivationListener
    • [接口方法] sessionDidActivate()与 sessionWillPassivate()
    • [接收事件] HttpSessionEvent
    • [触发场景] Activate与Passivate是用于置换对象的动作,当session对象为了资源 利用或负载平衡等原因而必须暂时储存至硬盘或其它储存器时(透 过对象序列化), 所作的动作称之为Passivate,而硬盘或储存器上的session对象重新加载JVM时所采 的动作称之为Activate,所以容 易理解的,sessionDidActivate()与 sessionWillPassivate()分别于Activeate后与将Passivate前呼叫
  6. ServletRequestListener
    • [接口方法] requestInitialized()与 requestDestroyed()
    • [接收事件] RequestEvent
    • [触发场景] 在request(HttpServletRequest)对象建立或被消灭时,会分别呼叫这两个方法。
  7. ServletRequestAttributeListener
    • [接口方法] attributeAdded()、 attributeReplaced()、attributeRemoved()
    • [接收事件] HttpSessionBindingEvent
    • [触发场景] 若有对象加入为request(HttpServletRequest)对象的属性,则会呼叫 attributeAdded(),同理在置换属性与移除属性时,会分别呼叫 attributeReplaced()、 attributeRemoved()
  8. HttpSessionBindingListener
    • [接口方法] valueBound()与valueUnbound()
    • [接收事件] HttpSessionBindingEvent
    • [触发场景] 实现HttpSessionBindingListener接口的类别,其实例如果被加入至 session(HttpSession)对象的属性中,则会 呼叫 valueBound(),如果被从 session(HttpSession)对象的属性中移除,则会呼叫valueUnbound(),实现 HttpSessionBindingListener接口的类别不需在web.xml中设定。

具体使用方法:在web.xml中添加如下语句:

<listener> 
  <listener-class>com.servlet.listener.YouAchieveListener<\listener-class>
<\listener>

其中YouAchieveListener为你实现的某个Listener接口的实现类com.servlet.listener.为 你的包名。

3 c#中的委托和事件

c#的委托和事件便用了观察者模式,事件是主题对象,委托是观察者。其中观察者的实现, c#中使用委托,java中使用接口,委托类似于c语言中的函数指针,它定义了方法的种类:

using System;
using System.Collections.Generic;
using System.Text;

namespace Delegate {
     //定义委托,它定义了可以代表的方法的类型
     public delegate void GreetingDelegate(string name);
        class Program {

           private static void EnglishGreeting(string name) {
               Console.WriteLine("Morning, " + name);
           }

           private static void ChineseGreeting(string name) {
               Console.WriteLine("早上好, " + name);
           }

           //注意此方法,它接受一个GreetingDelegate类型的方法作为参数
           private static void GreetPeople(string name, GreetingDelegate MakeGreeting) {
               MakeGreeting(name);
            }

           static void Main(string[] args) {
               GreetPeople("Jimmy Zhang", EnglishGreeting);
               GreetPeople("张子阳", ChineseGreeting);
               Console.ReadKey();
           }
        }
    }

但是c#中的委托实际上是一个类,可以将多个方法赋给同一个委托,或者叫将多个方法绑 定到同一个委托,当调用这个委托的时候,将依次调用其所绑定的方法。在这个例子中, 语法如下:

static void Main(string[] args) {
    GreetingDelegate delegate1;
    delegate1 = EnglishGreeting; // 先给委托类型的变量赋值
    delegate1 += ChineseGreeting;   // 给此委托变量再绑定一个方法

     // 将先后调用 EnglishGreeting 与 ChineseGreeting 方法
    GreetPeople("Jimmy Zhang", delegate1); 
    Console.ReadKey();
}

事件(Event)封装了委托类型的变量,使得:在类的内部,不管你声明它是public还是 protected,它总是private的。在类的外部,注册“+=”和注销“-=”的访问限定符与你 在声明事件时使用的访问符相同。其实没什么不好理解的,声明一个事件不过类似于声明 一个进行了封装的委托类型的变量而已,下面是一个事件示例:

public event GreetingDelegate MakeGreet;

我们进一步看下MakeGreet所产生的代码:

// 对事件的声明 实际是 声明一个私有的委托变量
private GreetingDelegate MakeGreet; 

[MethodImpl(MethodImplOptions.Synchronized)]
public void add_MakeGreet(GreetingDelegate value){
    this.MakeGreet = (GreetingDelegate) Delegate.Combine(this.MakeGreet, value);
}

[MethodImpl(MethodImplOptions.Synchronized)]
public void remove_MakeGreet(GreetingDelegate value){
    this.MakeGreet = (GreetingDelegate) Delegate.Remove(this.MakeGreet, value);
}

现在已经很明确了:MakeGreet事件确实是一个GreetingDelegate类型的委托,只不过不管 是不是声明为public,它总是被声明为private。另外,它还有两个方法,分别是 add_MakeGreet和remove_MakeGreet,这两个方法分别用于注册委托类型的方法和取消注册。 实际上也就是: “+= ”对应 add_MakeGreet,“-=”对应remove_MakeGreet。而这两个 方法的访问限制取决于声明事件时的访问限制符。

在add_MakeGreet()方法内部,实际上调用了System.Delegate的Combine()静态方法,这个 方法用于将当前的变量添加到委托链表中,事件就是观察者模式的主题对象,委托就是观 察者模式的观察者对象。

Date: \today

Author: machine of awareness

Org version 7.8.06 with Emacs version 23

Validate XHTML 1.0