1. 程式人生 > >Java設計模式:觀察者模式以及Servlet中的Listener

Java設計模式:觀察者模式以及Servlet中的Listener

觀察者模式(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.java
import java.util.ArrayList;

	/**
	 *Clickable 介面的實現類
	 * 觀察者介面
	 * @author HuiSir
	 */
public class Button implements Clickable {
	//儲存註冊過的單擊事件觀察者
	ArrayList<ClickableObserver>	observers = new ArrayList<ClickableObserver>();
	//按鈕資訊
	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種兩類事件的監聽器介面, 具體如下圖: 

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)如果某些觀察者的響應方法被阻塞,整個通知過程即被阻塞,其它觀察者不能及時被通知