Java设计模式:观察者模式以及Servlet中的Listener

来源:互联网 发布:java相关的书籍 编辑:程序博客网 时间:2024/06/13 00:01

观察者模式(Observer Pattern)也称发布订阅模式,它是一种在实际开发中经常用到的一种模式。

观察者模式定义:定义对象一种一对多的依赖关系,使得每当一个对象改变状态时,则所依赖它的对象会得到通知并被自动更新。

观察者类图如下:

201651994438920.png (471×286)

图1 观察者模式的类图 

观察者模式的角色如下:

Subject(抽象主题接口):定义了主题类中对观察者列表的一系列操作, 包括增加,删除, 通知等。
Concrete Subject(具体主题类):
Observer(抽象观察者接口):定义了观察者对主题类更新状态接受操作。
ConcreteObserver(具体观察者类):实现观察者接口更新主题类通知等逻辑。
观察者模式的应用场景:
  • 关联行为场景
  • 事件多级触发场景
  • 跨系统的消息交换场景,如消息队列的处理机制
观察者实例Demo : 使用观察者模式模拟按钮控件的事件处理机制。
Clickable.java/** * 被观察者接口 * @author HuiSir */public interface Clickable {//单击void click();//添加单击事件的观察者void addClickableObserver(ClickableObserver observer);//删除单击事件的观察者 void removeClickableObserver(ClickableObserver observer);}

ClickableObserver.java/** * 观察者接口 * @author HuiSir */public interface ClickableObserver {//发生单击事件时的操作void clicked(Clickable clickable);}

按钮控件,因按钮是可单击的控件,所以Button类实现Clickable接口。代码如下。

Button.javaimport java.util.ArrayList;/** *Clickable 接口的实现类 * 观察者接口 * @author HuiSir */public class Button implements Clickable {//存储注册过的单击事件观察者ArrayList<ClickableObserver>observers = new ArrayList<ClickableObserver>();//按钮信息String color; int x , y ;@Overridepublic void click() {System.out.println("按钮被单击");//执行所有观察者的事件的处理方法for(int i = observers.size() - 1 ; i >= 0 ; i--){ observers.get(i).clicked(this);}}@Overridepublic void addClickableObserver(ClickableObserver observer) { observers.add(observer);}@Overridepublic void removeClickableObserver(ClickableObserver observer) {observers.remove(observer);}@Overridepublic String toString(){return "按钮颜色: " + color + ",坐标" + x + "," + y ;}}
ChangeColorObserver.java/** * 观察按钮的颜色修改的观察者 * @author HuiSir */public class ChangeColorObserver implements ClickableObserver {@Overridepublic void clicked(Clickable clickable) {Button b = (Button)clickable;b.color = "红色" ;}}

ChangeCoordinateObserver.java/** * 观察坐标业务操作的观察者 * @author HuiSir */public class ChangeCoordinateObserver implements ClickableObserver {@Overridepublic void clicked(Clickable clickable) {// TODO Auto-generated method stubButton b = (Button)clickable;b.x = 100 ;b.y = 90 ;}}OtherObserver。javapublic class OtherObserver implements ClickableObserver {@Overridepublic void clicked(Clickable clickable) {// TODO Auto-generated method stubSystem.out.println("其他操作被执行");}}Test.javapublic class Test {/** * @author HuiSir * 测试类 */public static void main(String[] args) {Button button = new Button () ;button.color = "白色";button.x = 0 ;button.y = 0 ;button.addClickableObserver(new ChangeColorObserver());button.addClickableObserver(new ChangeCoordinateObserver());button.addClickableObserver(new OtherObserver());//button 的click 事件 单击后将触发在button中注册的观察者,然后观察者调用他的方法,从而//执行其对应的方法。很简单。button.click();System.out.println(button);}}Test.javapublic class Test {/** * @author HuiSir * 测试类 */public static void main(String[] args) {Button button = new Button () ;button.color = "白色";button.x = 0 ;button.y = 0 ;button.addClickableObserver(new ChangeColorObserver());button.addClickableObserver(new ChangeCoordinateObserver());button.addClickableObserver(new OtherObserver());//button 的click 事件 单击后将触发在button中注册的观察者,然后观察者调用他的方法,从而//执行其对应的方法。很简单。button.click();System.out.println(button);}}
运行结果如下:
按钮被单击
其他操作被执行
按钮颜色: 红色,坐标100,90


  从运行结果可以看出,按钮原来的“白色”、坐标“0,0”、单击按钮后,按钮的属性变为“红色”、坐标“100,90” ,充分演示了观察者模式在多级触发场景中的应用,体现了类之间的一种一对多的依赖关系。


Servlet中的Listener

  再说Servlet中的Listener之前, 先说说观察者模式的另一种形态——事件驱动模型。与上面提到的观察者模式的主题角色一样, 事件驱动模型包括事件源, 具体事件, 监听器, 具体监听器。 
Servlet中的Listener就是典型的事件驱动模型。 

JDK中有一套事件驱动的类, 包括一个统一的监听器接口和一个统一的事件源, 源码如下:

/** * A tagging interface that all event listener interfaces must extend. * @since JDK1.1 */public interface EventListener {}

ChangeColorObserver.java这是一个标志接口, JDK规定所有监听器必须继承这个接口。public class EventObject implements java.io.Serializable {  private static final long serialVersionUID = 5516075349620653480L;  /**   * The object on which the Event initially occurred.   */  protected transient Object source;  /**   * Constructs a prototypical Event.   *   * @param  source  The object on which the Event initially occurred.   * @exception IllegalArgumentException if source is null.   */  public EventObject(Object source) {    if (source == null)      throw new IllegalArgumentException("null source");    this.source = source;  }  /**   * The object on which the Event initially occurred.   *   * @return  The object on which the Event initially occurred.   */  public Object getSource() {    return source;  }  /**   * Returns a String representation of this EventObject.   *   * @return A a String representation of this EventObject.   */  public String toString() {    return getClass().getName() + "[source=" + source + "]";  }}
EvenObject是JDK给我们规定的一个统一的事件源。EvenObject类中定义了一个事件源以及获取事件源的get方法。
下面就分析一下Servlet Listener的运行流程。
Servlet Listener的组成
目前, Servlet中存在6种两类事件的监听器接口, 具体如下图: 

201651994659805.jpg (1406×918)

具体触发情境如下表:

201651994720693.jpg (1436×1470)

一个具体的Listener触发过程

我们以ServletRequestAttributeListener为例, 来分析一下此处事件驱动的流程。

首先一个Servlet中, HttpServletRequest调用setAttrilbute方法时, 实际上是调用的org.apache.catalina.connector.request#setAttrilbute方法。 我们看下它的源码:

public void setAttribute(String name, Object value) {    ...    //上面的逻辑代码已省略    // 此处即通知监听者    notifyAttributeAssigned(name, value, oldValue);  }
下面是notifyAttributeAssigned(String name, Object value, Object oldValue)的源码
private void notifyAttributeAssigned(String name, Object value,      Object oldValue) {    //从容器中获取webAPP中定义的Listener的实例对象    Object listeners[] = context.getApplicationEventListeners();    if ((listeners == null) || (listeners.length == 0)) {      return;    }    boolean replaced = (oldValue != null);    //创建相关事件对象    ServletRequestAttributeEvent event = null;    if (replaced) {      event = new ServletRequestAttributeEvent(          context.getServletContext(), getRequest(), name, oldValue);    } else {      event = new ServletRequestAttributeEvent(          context.getServletContext(), getRequest(), name, value);    }    //遍历所有监听器列表, 找到对应事件的监听器    for (int i = 0; i < listeners.length; i++) {      if (!(listeners[i] instanceof ServletRequestAttributeListener)) {        continue;      }      //调用监听器的方法, 实现监听操作      ServletRequestAttributeListener listener =        (ServletRequestAttributeListener) listeners[i];      try {        if (replaced) {          listener.attributeReplaced(event);        } else {          listener.attributeAdded(event);        }      } catch (Throwable t) {        ExceptionUtils.handleThrowable(t);        context.getLogger().error(sm.getString("coyoteRequest.attributeEvent"), t);        // Error valve will pick this exception up and display it to user        attributes.put(RequestDispatcher.ERROR_EXCEPTION, t);      }    }  }
上面的例子很清楚的看出ServletRequestAttributeListener是如何调用的。用户只需要实现监听器接口就行。Servlet中的Listener几乎涵盖了Servlet整个生命周期中你感兴趣的事件, 灵活运用这些Listenser可以使程序更加灵活。

总结

观察者模式定义了对象之间一对多的关系, 当一个对象(被观察者)的状态改变时, 依赖它的对象都会收到通知。可以应用到发布——订阅, 变化——更新这种业务场景中。
观察者和被观察者之间用松耦合的方式, 被观察者不知道观察者的细节, 只知道观察者实现了接口。
事件驱动模型更加灵活,但也是付出了系统的复杂性作为代价的,因为我们要为每一个事件源定制一个监听器以及事件,这会增加系统的负担。

观察者模式的核心是先分清角色、定位好观察者和被观察者、他们是多对一的关系。实现的关键是要建立观察者和被观察者之间的联系、比如在被观察者类中有个集合是用于存放观察者的、当被检测的东西发生改变的时候就要通知所有观察者。在观察者的构造方法中将被观察者传入、同时将本身注册到被观察者拥有的观察者名单中、即observers这个list中。

1.观察者模式优点:
(1)抽象主题只依赖于抽象观察者
(2)观察者模式支持广播通信
(3)观察者模式使信息产生层和响应层分离

2.观察者模式缺点:
(1)如一个主题被大量观察者注册,则通知所有观察者会花费较高代价
(2)如果某些观察者的响应方法被阻塞,整个通知过程即被阻塞,其它观察者不能及时被通知

                                             
0 0