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

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

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

观察者类图如下:

Java设计模式:观察者模式以及Servlet中的Listener_第1张图片

图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.java
import java.util.ArrayList;

	/**
	 *Clickable 接口的实现类
	 * 观察者接口
	 * @author HuiSir
	 */
public class Button implements Clickable {
	//存储注册过的单击事件观察者
	ArrayList	observers = new ArrayList();
	//按钮信息
	String color; 
	int x , y ;
	
	@Override
	public void click() {
		System.out.println("按钮被单击");
		//执行所有观察者的事件的处理方法
		for(int i = observers.size() - 1 ; i >= 0 ; i--){
			 observers.get(i).clicked(this);
		}
	}

	@Override
	public void addClickableObserver(ClickableObserver observer) {
		 observers.add(observer);
	}

	@Override
	public void removeClickableObserver(ClickableObserver observer) {
		observers.remove(observer);
	}

	@Override
	public String toString(){
		return "按钮颜色: " + color + ",坐标" + x + "," + y ;
	}
}
ChangeColorObserver.java
/**
 * 观察按钮的颜色修改的观察者
 * @author HuiSir
 */
public class ChangeColorObserver implements ClickableObserver {

	@Override
	public void clicked(Clickable clickable) {
		Button b = (Button)clickable;
		b.color = "红色" ;
	}
}

ChangeCoordinateObserver.java
/**
 * 观察坐标业务操作的观察者
 * @author HuiSir
 */
public class ChangeCoordinateObserver implements ClickableObserver {

	@Override
	public void clicked(Clickable clickable) {
		// TODO Auto-generated method stub
		Button b = (Button)clickable;
		b.x = 100 ;
		b.y = 90 ;
	}
}
OtherObserver。java
public class OtherObserver implements ClickableObserver {

	@Override
	public void clicked(Clickable clickable) {
		// TODO Auto-generated method stub
		System.out.println("其他操作被执行");
	}
}

Test.java
public 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.java
public 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种两类事件的监听器接口, 具体如下图: 

Java设计模式:观察者模式以及Servlet中的Listener_第2张图片

具体触发情境如下表:

Java设计模式:观察者模式以及Servlet中的Listener_第3张图片

一个具体的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)如果某些观察者的响应方法被阻塞,整个通知过程即被阻塞,其它观察者不能及时被通知

 
  
 
  
 
  
 
  
 
  
 
  
 
  
 
  
 
  
 
  
 
  
 
  
 
  
 
  
 
  
 
  
 
  
 
  
 
  
 
 

你可能感兴趣的:(JavaSE)