Java設計模式:觀察者模式以及Servlet中的Listener
觀察者模式(Observer Pattern)也稱釋出訂閱模式,它是一種在實際開發中經常用到的一種模式。
觀察者模式定義:定義物件一種一對多的依賴關係,使得每當一個物件改變狀態時,則所依賴它的物件會得到通知並被自動更新。
觀察者類圖如下:
圖1 觀察者模式的類圖
觀察者模式的角色如下:
Subject(抽象主題介面):定義了主題類中對觀察者列表的一系列操作, 包括增加,刪除, 通知等。 Concrete Subject(具體主題類): Observer(抽象觀察者介面):定義了觀察者對主題類更新狀態接受操作。 ConcreteObserver(具體觀察者類):實現觀察者介面更新主題類通知等邏輯。- 關聯行為場景
- 事件多級觸發場景
- 跨系統的訊息交換場景,如訊息佇列的處理機制
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種兩類事件的監聽器介面, 具體如下圖:
具體觸發情境如下表:
一個具體的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)如果某些觀察者的響應方法被阻塞,整個通知過程即被阻塞,其它觀察者不能及時被通知