執行個體解析觀察者模式及其在Java設計模式開發中的運用_java

來源:互聯網
上載者:User

一、觀察者模式(Observer)的定義:

觀察者模式又稱為訂閱—發布模式,在此模式中,一個目標對象管理所有相依於它的觀察者對象,並且在它本身的狀態改變時主動發出通知。這通常透過呼叫各觀察者所提供的方法來實現。此種模式通常被用來事件處理系統。

1、觀察者模式的一般結構

首先看下觀察者模式的類圖描述:

觀察者模式的角色如下:

Subject(抽象主題介面):定義了主題類中對觀察者列表的一系列操作, 包括增加,刪除, 通知等。
Concrete Subject(具體主題類):
Observer(抽象觀察者介面):定義了觀察者對主題類更新狀態接受操作。
ConcreteObserver(具體觀察者類):實現觀察者介面更新主題類通知等邏輯。
從這個類圖可以看出, 主題類中維護了一個實現觀察者介面的類列表, 主題類通過這個列表來對觀察者進行一系列的增刪改操作。觀察者類也可以主動調用update方法來瞭解擷取主題類的狀態更新資訊。

以上的類圖所描述的只是基本的觀察者模式的思想, 有很多不足。比如作為觀察者也可以主動訂閱某類主題等。下面的例子將進行一些改動, 以便適用具體的商務邏輯。

2、觀察者模式樣本

我們構建一個觀察者和主題類, 觀察者可以主動訂閱主題或者取消主題。主題類統一被一個主旨管理員者所管理。下面給出類圖:

Subject:

public interface Subject {  //註冊一個observer  public void register(Observer observer);  //移除一個observer  public void remove(Observer observer);  //通知所有觀察者  public void notifyObservers();  //擷取主題類要發布的訊息  public String getMessage();}ConcerteSubject:public class MySubject implements Subject {  private List<Observer> observers;  private boolean changed;  private String message;  //對象鎖, 用於同步更新觀察者列表  private final Object mutex = new Object();  public MySubject() {    observers = new ArrayList<Observer>();    changed = false;  }  @Override  public void register(Observer observer) {    if (observer == null)      throw new NullPointerException();      //保證不重複    if (!observers.contains(observer))      observers.add(observer);  }  @Override  public void remove(Observer observer) {    observers.remove(observer);  }  @Override  public void notifyObservers() {    // temp list    List<Observer> tempObservers = null;    synchronized (mutex) {      if (!changed)        return;      tempObservers = new ArrayList<>(this.observers);      this.changed = false;    }    for(Observer obj : tempObservers) {      obj.update();    }  }  //主題類發布新訊息  public void makeChanged(String message) {    System.out.println("The Subject make a change: " + message);    this.message = message;    this.changed = true;    notifyObservers();  }  @Override  public String getMessage() {    return this.message;  }}

ConcerteSubject做出更新時, 就通知清單中的所有觀察者, 並且調用觀察者update方法以實現接受通知後的邏輯。這裡注意notifyObservers中的同步塊。在多線程的情況下, 為了避免主題類發布通知時, 其他線程對觀察者列表的增刪操作, 同步塊中用一個臨時List來擷取當前的觀察者列表。

SubjectManagement:主題類管理器

public class SubjectManagement {  //一個記錄 名字——主題類 的Map  private Map<String, Subject> subjectList = new HashMap<String, Subject>();  public void addSubject(String name, Subject subject) {    subjectList.put(name, subject);  }  public void addSubject(Subject subject) {    subjectList.put(subject.getClass().getName(), subject);  }  public Subject getSubject(String subjectName) {    return subjectList.get(subjectName);  }  public void removeSubject(String name, Subject subject) {  }  public void removeSubject(Subject subject) {  }  //singleton  private SubjectManagement() {}  public static SubjectManagement getInstance() {    return SubjectManagementInstance.instance;  }  private static class SubjectManagementInstance {    static final SubjectManagement instance = new SubjectManagement();  }}

主題類管理器的作用就是在觀察者訂閱某個主題時, 擷取此主題的執行個體對象。

Observer:

public interface Observer {  public void update();  public void setSubject(Subject subject);}ConcerteObserver:public class MyObserver implements Observer {  private Subject subject;  // get the notify message from Concentrate Subject  @Override  public void update() {    String message = subject.getMessage();    System.out.println("From Subject " + subject.getClass().getName()        + " message: " + message);  }  @Override  public void setSubject(Subject subject) {    this.subject = subject;  }  // subcirbe some Subject  public void subscribe(String subjectName) {    SubjectManagement.getInstance().getSubject(subjectName).register(this);  }  // cancel subcribe  public void cancelSubcribe(String subjectName) {    SubjectManagement.getInstance().getSubject(subjectName).remove(this);  }}

測試:我們將主題類和觀察者抽象成寫者和讀者

public class ObserverTest {  private static MySubject writer;  @BeforeClass  public static void setUpBeforeClass() throws Exception {    writer = new MySubject();    //添加一個名為Linus的作家    SubjectManagement.getInstance().addSubject("Linus",writer);  }  @Test  public void test() {    //定義幾個讀者    MyObserver reader1 = new MyObserver();    MyObserver reader2 = new MyObserver();    MyObserver reader3 = new MyObserver();    reader1.setSubject(writer);    reader2.setSubject(writer);    reader3.setSubject(writer);    reader1.subscribe("Linus");    reader2.subscribe("Linus");    reader3.subscribe("Linus");    writer.makeChanged("I have a new Changed");    reader1.update();  }}

以上就是觀察者模式的小樣本。可以看出每個主題類都要維護一個相應的觀察者列表, 這裡可以根據具體主題的抽象層次進一步抽象, 將這種聚集放到一個抽象類別中去實現, 來共同維護一個列表, 當然具體操作要看實際的商務邏輯。

二、Servlet中的Listener

再說Servlet中的Listener之前, 先說說觀察者模式的另一種形態——事件驅動模型。與上面提到的觀察者模式的主題角色一樣, 事件驅動模型包括事件來源, 具體事件, 監聽器, 具體監聽器。
Servlet中的Listener就是典型的事件驅動模型。
JDK中有一套事件驅動的類, 包括一個統一的監聽器介面和一個統一的事件來源, 源碼如下:

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

這是一個標誌介面, 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的運行流程。

1、Servlet Listener的組成

目前, Servlet中存在6種兩類事件的監聽器介面, 具體如下圖:

具體觸發情境如下表:

2、一個具體的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可以使程式更加靈活。

三、綜合樣本
舉個例子,如果你看過TVB的警匪片,你就知道臥底的工作方式。一般一個警察可能有幾個臥底,潛入敵人內部,打探訊息,臥底完全靠他的領導的指示幹活,領導說幾點行動,他必須按照這個時間去執行,如果行動時間改變,他也要立馬改變自己配合行動的時間。領導派兩個臥底去打入敵人內部,那麼領導相當於抽象主題,而督察警官張三這個人派了兩個臥底李四和萬王五,張三就相當於具體主題,臥底相當於抽象觀察者,這兩名臥底是李四和王五就是具體觀察者,派的這個動作相當於觀察者在主題的登記。那麼這個類圖如下:

利用javaAPI來實現,代碼描述如下:

package observer;  import java.util.List; import java.util.Observable; import java.util.Observer; /**  *描述:警察張三  */ public class Police extends Observable {    private String time ;   public Police(List<Observer> list) {     super();     for (Observer o:list) {       addObserver(o);     }   }   public void change(String time){     this.time = time;     setChanged();     notifyObservers(this.time);   } } 
package observer;  import java.util.Observable; import java.util.Observer; /**  *描述:臥底A  */ public class UndercoverA implements Observer {    private String time;   @Override   public void update(Observable o, Object arg) {     time = (String) arg;     System.out.println("臥底A接到訊息,行動時間為:"+time);   }   } 
package observer;  import java.util.Observable; import java.util.Observer; /**  *描述:臥底B  */ public class UndercoverB implements Observer {   private String time;   @Override   public void update(Observable o, Object arg) {     time = (String) arg;     System.out.println("臥底B接到訊息,行動時間為:"+time);   }    } 
package observer;  import java.util.ArrayList; import java.util.List; import java.util.Observer; /**  *描述:測試  */ public class Client {    /**    * @param args    */   public static void main(String[] args) {     UndercoverA o1 = new UndercoverA();     UndercoverB o2 = new UndercoverB();     List<Observer> list = new ArrayList<>();     list.add(o1);     list.add(o2);     Police subject = new Police(list);     subject.change("02:25");     System.out.println("===========由於訊息敗露,行動時間提前=========");     subject.change("01:05");        }  } 

測試回合結果:

臥底B接到訊息,行動時間為:02:25臥底A接到訊息,行動時間為:02:25===========由於訊息敗露,行動時間提前=========臥底B接到訊息,行動時間為:01:05臥底A接到訊息,行動時間為:01:05

四、總結

觀察者模式定義了對象之間一對多的關係, 當一個對象(被觀察者)的狀態改變時, 依賴它的對象都會收到通知。可以應用到發布——訂閱, 變化——更新這種業務情境中。
觀察者和被觀察者之間用松耦合的方式, 被觀察者不知道觀察者的細節, 只知道觀察者實現了介面。
事件驅動模型更加靈活,但也是付出了系統的複雜性作為代價的,因為我們要為每一個事件來源定製一個監聽器以及事件,這會增加系統的負擔。

觀察者模式的核心是先分清角色、定位好觀察者和被觀察者、他們是多對一的關係。實現的關鍵是要建立觀察者和被觀察者之間的聯絡、比如在被觀察者類中有個集合是用於存放觀察者的、當被檢測的東西發生改變的時候就要通知所有觀察者。在觀察者的構造方法中將被觀察者傳入、同時將本身註冊到被觀察者擁有的觀察者名單中、即observers這個list中。

1.觀察者模式優點:
(1)抽象主題只依賴於抽象觀察者
(2)觀察者模式支援廣播通訊
(3)觀察者模式使資訊產生層和響應層分離

2.觀察者模式缺點:
(1)如一個主題被大量觀察者註冊,則通知所有觀察者會花費較高代價
(2)如果某些觀察者的回應程式法被阻塞,整個通知過程即被阻塞,其它觀察者不能及時被通知

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.