zoukankan      html  css  js  c++  java
  • 观察者模式

    观察者模式

    观察者模式

    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: oday

    Author: machine of awareness

    Org version 7.8.06 with Emacs version 23

    Validate XHTML 1.0
  • 相关阅读:
    《DSP using MATLAB》 示例 Example 9.12
    《DSP using MATLAB》示例 Example 9.11
    《DSP using MATLAB》示例 Example 9.10
    《DSP using MATLAB》示例Example 9.9
    《DSP using MATLAB》示例 Example 9.8
    《DSP using MATLAB》示例Example 9.7
    《DSP using MATLAB》示例 Example 9.6
    《DSP using MATLAB》示例Example 9.5
    《DSP using MATLAB》示例 Example 9.4
    (转载)【C++11新特性】 nullptr关键字
  • 原文地址:https://www.cnblogs.com/machine/p/3262378.html
Copyright © 2011-2022 走看看