轉載自 http://blog.csdn.net/zuoxiaolong8810/article/details/9081079
作者:zuoxiaolong8810(左瀟龍),轉載請註明出處。
本章我們討論一個除前面的單例以及代理模式之外,一個WEB專案中有可能用到的設計模式,即觀察者模式。
說起觀察者模式,LZ還是非常激動的,當初這算是第一個讓LZ感受到設計模式強大的傢伙。當初LZ要做一個小型WEB專案,要上傳給伺服器檔案,一個需求就是要顯示上傳進度,LZ就是用這個模式解決了當時的問題,那時LZ接觸Java剛幾個月,比葫蘆畫瓢的用了觀察者模式。
現在談及觀察者模式,能用到的地方就相對較多了,通常意義上如果一個物件狀態的改變需要通知很多對這個物件關注的一系列物件,就可以使用觀察者模式。
下面LZ先給出觀察者模式標準版的定義,引自百度百科。
定義:觀察者模式(有時又被稱為釋出-訂閱模式、模型-檢視模式、源-收聽者模式或從屬者模式)是軟體設計模式的一種。在此種模式中,一個目標物件管理所有相依於它的觀察者物件,並且在它本身的狀態改變時主動發出通知。這通常透過呼叫各觀察者所提供的方法來實現。此種模式通常被用來實作事件處理系統。
上面的定義當中,主要有這樣幾個意思,首先是有一個目標的物件,通俗點講就是一個類,它管理了所有依賴於它的觀察者物件,或者通俗點說是觀察者類,並在它自己狀態發生變化時,主動發出通知。
簡單點概括成通俗的話來說,就是一個類管理著所有依賴於它的觀察者類,並且它狀態變化時會主動給這些依賴它的類發出通知。
那麼我們針對上面的描述給出觀察者模式的類圖,百度百科沒有給出觀察者模式的類圖,這裡LZ自己使用工具給各位畫一個。
可以看到,我們的被觀察者類Observable只關聯了一個Observer的列表,然後在自己狀態變化時,使用notifyObservers方法通知這些Observer,具體這些Observer都是什麼,被觀察者是不關心也不需要知道的。
上面就將觀察者和被觀察者二者的耦合度降到很低了,而我們具體的觀察者是必須要知道自己觀察的是誰,所以它依賴於被觀察者。
下面LZ給寫出一個很簡單的觀察者模式,來使用JAVA程式碼簡單詮釋一下上面的類圖。
首先是觀察者介面。
-
package net;
-
-
-
public interface Observer {
-
-
void update(Observable o);
-
-
}
再者是具體的觀察者。
-
package net;
-
-
public class ConcreteObserver1 implements Observer{
-
-
public void update(Observable o) {
-
System.out.println("觀察者1觀察到" + o.getClass().getSimpleName() + "發生變化");
-
System.out.println("觀察者1做出相應");
-
}
-
-
}
-
package net;
-
-
public class ConcreteObserver2 implements Observer{
-
-
public void update(Observable o) {
-
System.out.println("觀察者2觀察到" + o.getClass().getSimpleName() + "發生變化");
-
System.out.println("觀察者2做出相應");
-
}
-
-
}
下面是被觀察者,它有一個觀察者的列表,並且有一個通知所有觀察者的方法,通知的方式就是呼叫觀察者通用的介面行為update方法。下面我們看它的程式碼。
-
package net;
-
-
import java.util.ArrayList;
-
import java.util.List;
-
-
public class Observable {
-
-
List<Observer> observers = new ArrayList<Observer>();
-
-
public void addObserver(Observer o){
-
observers.add(o);
-
}
-
-
public void changed(){
-
System.out.println("我是被觀察者,我已經發生變化了");
-
notifyObservers();
-
}
-
-
public void notifyObservers(){
-
for (Observer observer : observers) {
-
observer.update(this);
-
}
-
}
-
}
這裡面很簡單,新增兩個方法,一個是為了改變自己的同時通知觀察者們,一個是為了給客戶端一個新增觀察者的公共介面。
下面我們使用客戶端呼叫一下,看一下客戶端如何操作。
-
package net;
-
-
-
public class Client {
-
-
public static void main(String[] args) throws Exception {
-
Observable observable = new Observable();
-
observable.addObserver(new ConcreteObserver1());
-
observable.addObserver(new ConcreteObserver2());
-
-
observable.changed();
-
}
-
}
執行結果如下。
可以看到我們在操作被觀察者時,只要呼叫changed方法,觀察者們就會做出相應的動作,而新增觀察者這個行為算是準備階段,將具體的觀察者關聯到被觀察者上面去。
下面LZ給出一個有實際意義的例子,比如我們經常看的小說網站,都有這樣的功能,就是讀者可以訂閱作者,這當中就有明顯的觀察者模式案例,就是作者和讀者。他們的關係是一旦讀者關注了一個作者,那麼這個作者一旦有什麼新書,就都要通知讀者們,這明顯是一個觀察者模式的案例,所以我們可以使用觀察者模式解決。
由於JDK中為了方便開發人員,已經寫好了現成的觀察者介面和被觀察者類,下面LZ先給出JDK中現成的觀察者和被觀察者程式碼,外加自己的一點解釋,來幫助一些讀者對JDK中對觀察者模式的支援熟悉一下。
先來觀察者介面。
-
-
public interface Observer {
-
-
void update(Observable o, Object arg);
-
-
}
下面是被觀察者類。
-
import java.util.Vector;
-
-
-
public class Observable {
-
-
private boolean changed = false;
-
-
private Vector obs;
-
-
public Observable() {
-
obs = new Vector();
-
}
-
-
public synchronized void addObserver(Observer o) {
-
if (o == null)
-
throw new NullPointerException();
-
if (!obs.contains(o)) {
-
obs.addElement(o);
-
}
-
}
-
-
public synchronized void deleteObserver(Observer o) {
-
obs.removeElement(o);
-
}
-
-
public void notifyObservers() {
-
notifyObservers(null);
-
}
-
-
public void notifyObservers(Object arg) {
-
-
Object[] arrLocal;
-
-
-
synchronized (this) {
-
-
if (!changed)
-
return;
-
-
arrLocal = obs.toArray();
-
-
clearChanged();
-
}
-
-
-
-
for (int i = arrLocal.length-1; i>=0; i--)
-
((Observer)arrLocal[i]).update(this, arg);
-
}
-
-
-
public synchronized void deleteObservers() {
-
obs.removeAllElements();
-
}
-
-
-
protected synchronized void setChanged() {
-
changed = true;
-
}
-
-
protected synchronized void clearChanged() {
-
changed = false;
-
}
-
-
public synchronized boolean hasChanged() {
-
return changed;
-
}
-
-
public synchronized int countObservers() {
-
return obs.size();
-
}
-
}
被觀察者除了一點同步的地方需要特殊解釋一下,其餘的相信各位都能看明白各個方法的用途。其實上述JDK的類是有漏洞的,或者說,在我們使用觀察者模式時要注意一個問題,就是notifyObservers這個方法中的這一段程式碼。
-
for (int i = arrLocal.length-1; i>=0; i--)
-
((Observer)arrLocal[i]).update(this, arg);
在迴圈遍歷觀察者讓觀察者做出響應時,JDK沒有去抓取update方法中的異常,所以假設在這過程中有一個update方法丟擲了異常,那麼剩下還未通知的觀察者就全都通知不到了,所以LZ個人比較疑惑這樣的用意(LZ無法想象JAVA類庫的製造者沒考慮到這個問題),是sun當時真的忘了考慮這一點,還是另有它意?當然各位讀者如果有自己的見解可以告知LZ,不過LZ認為,不管是sun如此做是別有用意,還是真的欠考慮,我們都要注意在update方法裡一定要處理好異常,個人覺得JDK中比較保險的做法還是如下這樣。
-
for (int i = arrLocal.length-1; i>=0; i--){
-
try {
-
((Observer)arrLocal[i]).update(this, arg);
-
} catch (Throwable e) {e.printStackTrace();}
-
}
這樣無論其中任何一個update是否成功都不會影響其餘的觀察者進行更新狀態,我們自己比較保險的做法就是給update方法整個加上try塊,或者確認不會發生執行時異常。
上面LZ和各位一起分析了JDK中觀察者模式的原始碼,下面我們就拿上述小說網的例子,做一個DEMO。
首先要搞清楚在讀者和作者之間是誰觀察誰,很明顯,應該是讀者觀察作者。所以作者是被觀察者,讀者是觀察者,除了這兩個類之外,我們還需要額外新增一個管理器幫我們管理下作者的列表便於讀者關注,於是一個觀察者模式的DEMO就出現了。如下,首先是讀者類,LZ在各個類都加了點註釋。
-
-
public class Reader implements Observer{
-
-
private String name;
-
-
public Reader(String name) {
-
super();
-
this.name = name;
-
}
-
-
public String getName() {
-
return name;
-
}
-
-
-
public void subscribe(String writerName){
-
WriterManager.getInstance().getWriter(writerName).addObserver(this);
-
}
-
-
-
public void unsubscribe(String writerName){
-
WriterManager.getInstance().getWriter(writerName).deleteObserver(this);
-
}
-
-
-
public void update(Observable o, Object obj) {
-
if (o instanceof Writer) {
-
Writer writer = (Writer) o;
-
System.out.println(name+"知道" + writer.getName() + "釋出了新書《" + writer.getLastNovel() + "》,非要去看!");
-
}
-
}
-
-
}
下面是作者類。
-
-
public class Writer extends Observable{
-
-
private String name;
-
-
private String lastNovel;
-
-
public Writer(String name) {
-
super();
-
this.name = name;
-
WriterManager.getInstance().add(this);
-
}
-
-
-
public void addNovel(String novel) {
-
System.out.println(name + "釋出了新書《" + novel + "》!");
-
lastNovel = novel;
-
setChanged();
-
notifyObservers();
-
}
-
-
public String getLastNovel() {
-
return lastNovel;
-
}
-
-
public String getName() {
-
return name;
-
}
-
-
}
然後我們還需要一個管理器幫我們管理這些作者。如下。
-
import java.util.HashMap;
-
import java.util.Map;
-
-
-
public class WriterManager{
-
-
private Map<String, Writer> writerMap = new HashMap<String, Writer>();
-
-
-
public void add(Writer writer){
-
writerMap.put(writer.getName(), writer);
-
}
-
-
public Writer getWriter(String name){
-
return writerMap.get(name);
-
}
-
-
-
private WriterManager(){}
-
-
public static WriterManager getInstance(){
-
return WriterManagerInstance.instance;
-
}
-
private static class WriterManagerInstance{
-
-
private static WriterManager instance = new WriterManager();
-
-
}
-
}
好了,這下我們的觀察者模式就做好了,這個簡單的DEMO可以支援讀者關注作者,當作者釋出新書時,讀者會觀察到這個事情,會產生相應的動作。下面我們寫個客戶端呼叫一下。
-
-
public class Client {
-
-
public static void main(String[] args) {
-
-
Reader r1 = new Reader("謝廣坤");
-
Reader r2 = new Reader("趙四");
-
Reader r3 = new Reader("七哥");
-
Reader r4 = new Reader("劉能");
-
Writer w1 = new Writer("謝大腳");
-
Writer w2 = new Writer("王小蒙");
-
-
r1.subscribe("謝大腳");
-
r2.subscribe("謝大腳");
-
r3.subscribe("謝大腳");
-
r4.subscribe("謝大腳");
-
-
r3.subscribe("王小蒙");
-
r4.subscribe("王小蒙");
-
-
-
-
w1.addNovel("設計模式");
-
-
w2.addNovel("JAVA程式設計思想");
-
-
r1.unsubscribe("謝大腳");
-
-
w1.addNovel("觀察者模式");
-
}
-
-
}
看下我們得到的結果,就會發現,我們確實通知了讀者它所關注的作者的動態,而且讀者取消關注以後,作者的動態將不再通知該讀者。下面是執行結果。
我們使用觀察者模式的用意是為了作者不再需要關心他釋出新書時都要去通知誰,更重要的是他不需要關心他通知的是讀者還是其它什麼人,他只知道這個人是實現了觀察者介面的,即我們的被觀察者依賴的只是一個抽象的介面觀察者介面,而不關心具體的觀察者都有誰都是什麼,比如以後要是遊客也可以關注作者了,那麼只要遊客類實現觀察者介面,那麼一樣可以將遊客列入到作者的觀察者列表中。
另外,我們讓讀者自己來選擇自己關注的物件,這相當於被觀察者將維護通知物件的職能轉化給了觀察者,這樣做的好處是由於一個被觀察者可能有N多觀察者,所以讓被觀察者自己維護這個列表會很艱難,這就像一個老師被許多學生認識,那麼是所有的學生都記住老師的名字簡單,還是讓老師記住N多學生的名字簡單?答案顯而易見,讓學生們都記住一個老師的名字是最簡單的。
另外,觀察者模式分離了觀察者和被觀察者二者的責任,這樣讓類之間各自維護自己的功能,專注於自己的功能,會提高系統的可維護性和可重用性。
觀察者模式其實還有另外一種形態,就是事件驅動模型,LZ個人覺得這兩種方式大體上其實是非常相似的,所以LZ決定一起引入事件驅動模型。不過觀察者更多的強調的是釋出-訂閱式的問題處理,而事件驅動則更多的注重於介面與資料模型之間的問題,兩者還是有很多適用場景上的區別的,雖不能一概而論,但放在一起討論還是很方便各位理解二者。
說到事件驅動,由於JAVA在桌面應用程式方面有很多欠缺,所以swing的使用其實並不是特別廣泛,因為你不可能要求大多數人的機子上都安裝了JDK,除非你是給特殊使用者人群開發的應用程式,這些使用者在你的可控範圍內,那麼swing或許可以派上用場。
考慮到學習JAVA或者使用JAVA的人群大部分都是在進行web開發,所以本次討論事件驅動,採用web開發當中所用到的示例。
相信各位都知道tomcat,這是一個app伺服器,在使用的過程中,或許經常會有人用到listener,即監聽器這個概念。那麼其實這個就是一個事件驅動模型的應用。比如我們的spring,我們在應用啟動的時候要初始化我們的IOC容器,那麼我們的做法就是加入一個listener,這樣伴隨著tomcat伺服器的啟動,spring的IOC容器就會跟著啟動。
那麼這個listener其實就是事件驅動模型中的監聽器,它用來監聽它所感興趣的事,比如我們springIOC容器啟動的監聽器,就是實現的ServletContextListener這個介面,說明它對servletContext感興趣,會監聽servletContext的啟動和銷燬。
LZ不打算使用這個例子作為講解,因為它的內部運作比較複雜,需要搬上來tomcat的原始碼,對於新手來說,這是個噩耗,所以我們將上述的例子改為事件驅動來實現。也好讓各位針對性的對比觀察者模式和事件驅動模型。
首先事件驅動模型與觀察者模式勉強的對應關係可以看成是,被觀察者相當於事件源,觀察者相當於監聽器,事件源會產生事件,監聽器監聽事件。所以這其中就攙和到四個類,事件源,事件,監聽器以及具體的監聽器。
JDK當中依然有現成的一套事件模型類庫,其中監聽器只是一個標識介面,因為它沒有表達對具體物件感興趣的意思,所以也無法定義監聽的事件,只是為了統一,用來給特定的監聽器繼承。它的原始碼如下。
-
package java.util;
-
-
-
-
-
-
public interface EventListener {
-
}
由於程式碼很短,所以LZ沒有刪減,當中標註了,所有的事件監聽器都必須繼承,這是一個標識介面。上述的事件,JDK當中也有一個現成的類供繼承,就是EventObject,這個類的原始碼如下。
-
public class EventObject implements java.io.Serializable {
-
-
private static final long serialVersionUID = 5516075349620653480L;
-
-
-
-
-
protected transient Object source;
-
-
-
-
-
-
-
-
public EventObject(Object source) {
-
if (source == null)
-
throw new IllegalArgumentException("null source");
-
-
this.source = source;
-
}
-
-
-
-
-
-
-
public Object getSource() {
-
return source;
-
}
-
-
-
-
-
-
-
public String toString() {
-
return getClass().getName() + "[source=" + source + "]";
-
}
-
}
這個類並不複雜,它只是想表明,所有的事件都應該帶有一個事件源,大部分情況下,這個事件源就是我們被監聽的物件。
如果我們採用事件驅動模型去分析上面的例子,那麼作者就是事件源,而讀者就是監聽器,依據這個思想,我們把上述例子改一下,首先我們需要自定義我們自己的監聽器和事件。所以我們定義如下作者事件。
-
import java.util.EventObject;
-
-
public class WriterEvent extends EventObject{
-
-
private static final long serialVersionUID = 8546459078247503692L;
-
-
public WriterEvent(Writer writer) {
-
super(writer);
-
}
-
-
public Writer getWriter(){
-
return (Writer) super.getSource();
-
}
-
-
}
這代表了一個作者事件,這個事件當中一般就是包含一個事件源,在這裡就是作者,當然有的時候你可以讓它帶有更多的資訊,以方便監聽器做出更加細緻的動作。下面我們定義如下監聽器。
-
import java.util.EventListener;
-
-
public interface WriterListener extends EventListener{
-
-
void addNovel(WriterEvent writerEvent);
-
-
}
這個監聽器猛地一看,特別像觀察者介面,它們承擔的功能是類似的,都是提供觀察者或者監聽者實現自己響應的行為規定,其中addNovel方法代表的是作者釋出新書時的響應。加入了這兩個類以後,我們原有的作者和讀者類就要發生點變化了,我們先來看作者類的變化。
-
import java.util.HashSet;
-
import java.util.Set;
-
-
-
public class Writer{
-
-
private String name;
-
-
private String lastNovel;
-
-
private Set<WriterListener> writerListenerList = new HashSet<WriterListener>();
-
-
public Writer(String name) {
-
super();
-
this.name = name;
-
WriterManager.getInstance().add(this);
-
}
-
-
-
public void addNovel(String novel) {
-
System.out.println(name + "釋出了新書《" + novel + "》!");
-
lastNovel = novel;
-
fireEvent();
-
}
-
-
private void fireEvent(){
-
WriterEvent writerEvent = new WriterEvent(this);
-
for (WriterListener writerListener : writerListenerList) {
-
writerListener.addNovel(writerEvent);
-
}
-
}
-
-
public void registerListener(WriterListener writerListener){
-
writerListenerList.add(writerListener);
-
}
-
-
public void unregisterListener(WriterListener writerListener){
-
writerListenerList.remove(writerListener);
-
}
-
-
public String getLastNovel() {
-
return lastNovel;
-
}
-
-
public String getName() {
-
return name;
-
}
-
-
}
可以看到,作者類的主要變化是新增了一個自己的監聽器列表,我們使用set是為了它的天然去重效果,並且提供給外部註冊和登出的方法,與觀察者模式相比,這個功能本身是由基類Observable提供的,不過觀察者模式中有統一的觀察者Observer介面,但是監聽器沒有,雖說有EventListener這個超級介面,但它畢竟沒有任何行為。所以我們一般需要維持一個自己特有的監聽器列表。
下面我們看讀者類的變化,如下。
-
public class Reader implements WriterListener{
-
-
private String name;
-
-
public Reader(String name) {
-
super();
-
this.name = name;
-
}
-
-
public String getName() {
-
return name;
-
}
-
-
-
public void subscribe(String writerName){
-
WriterManager.getInstance().getWriter(writerName).registerListener(this);
-
}
-
-
-
public void unsubscribe(String writerName){
-
WriterManager.getInstance().getWriter(writerName).unregisterListener(this);
-
}
-
-
public void addNovel(WriterEvent writerEvent) {
-
Writer writer = writerEvent.getWriter();
-
System.out.println(name+"知道" + writer.getName() + "釋出了新書《" + writer.getLastNovel() + "》,非要去看!");
-
}
-
-
}
讀者類的變化,首先本來是實現Observer介面,現在要實現WriterListener介面,響應的update方法就改為我們定義的addNovel方法,當中的響應基本沒變。另外就是關注和取消關注的方法中,原來是給作者類新增觀察者和刪除觀察者,現在是註冊監聽器和登出監聽器,幾乎是沒什麼變化的。
我們徹底將剛才的觀察者模式改成了事件驅動,現在我們使用事件驅動的類再執行一下客戶端,其中客戶端程式碼和WriterManager類的程式碼是完全不需要改動的,直接執行客戶端即可。我們會發現得到的結果與觀察者模式一模一樣。
走到這裡我們發現二者可以達到的效果一模一樣,那麼兩者是不是一樣呢?
答案當然是否定的,首先我們從實現方式上就能看出,事件驅動可以解決觀察者模式的問題,但反過來則不一定,另外二者所表達的業務場景也不一樣,比如上述例子,使用觀察者模式更貼近業務場景的描述,而使用事件驅動,從業務上講,則有點勉強。
二者除了業務場景的區別以外,在功能上主要有以下區別。
1,觀察者模式中觀察者的響應理論上講針對特定的被觀察者是唯一的(說理論上唯一的原因是,如果你願意,你完全可以在update方法裡新增一系列的elseif去產生不同的響應,但LZ早就說過,你應該忘掉elseif),而事件驅動則不是,因為我們可以定義自己感興趣的事情,比如剛才,我們可以監聽作者釋出新書,我們還可以在監聽器介面中定義其它的行為。再比如tomcat中,我們可以監聽servletcontext的init動作,也可以監聽它的destroy動作。
2,雖然事件驅動模型更加靈活,但也是付出了系統的複雜性作為代價的,因為我們要為每一個事件源定製一個監聽器以及事件,這會增加系統的負擔,各位看看tomcat中有多少個監聽器和事件類就知道了。
3,另外觀察者模式要求被觀察者繼承Observable類,這就意味著如果被觀察者原來有父類的話,就需要自己實現被觀察者的功能,當然,這一尷尬事情,我們可以使用介面卡模式彌補,但也不可避免的造成了觀察者模式的侷限性。事件驅動中事件源則不需要,因為事件源所維護的監聽器列表是給自己定製的,所以無法去製作一個通用的父類去完成這個工作。
4,被觀察者傳送給觀察者的資訊是模糊的,比如update中第二個引數,型別是Object,這需要觀察者和被觀察者之間有約定才可以使用這個引數。而在事件驅動模型中,這些資訊是被封裝在Event當中的,可以更清楚的告訴監聽器,每個資訊都是代表的什麼。
由於上述使用事件驅動有點勉強,所以LZ給各位模擬一個我們js當中的一個事件驅動模型,就是按鈕的點選事件。
在這個模型當中,按鈕自然就是事件源,而事件的種類有很多,比如點選(click),雙擊(dblclick),滑鼠移動事件(mousemove)。我們的監聽器與事件個數是一樣的,所以這也是事件驅動的弊端,我們需要一堆事件和監聽器,下面LZ一次性給出這三種事件和監聽器,其餘還有很多事件,類似,LZ這裡省略。
-
import java.util.EventObject;
-
-
public abstract class ButtonEvent extends EventObject{
-
-
public ButtonEvent(Object source) {
-
super(source);
-
}
-
-
public Button getButton(){
-
return (Button) super.getSource();
-
}
-
}
-
-
class ClickEvent extends ButtonEvent{
-
-
public ClickEvent(Object source) {
-
super(source);
-
}
-
-
}
-
-
class DblClickEvent extends ButtonEvent{
-
-
public DblClickEvent(Object source) {
-
super(source);
-
}
-
-
}
-
-
class MouseMoveEvent extends ButtonEvent{
-
-
private int x;
-
private int y;
-
-
public MouseMoveEvent(Object source, int x, int y) {
-
super(source);
-
this.x = x;
-
this.y = y;
-
}
-
-
public int getX() {
-
return x;
-
}
-
-
public int getY() {
-
return y;
-
}
-
-
}
以上是三種事件,都非常簡單,只有滑鼠移動需要額外的座標,下面給出三種監聽器。
-
import java.util.EventListener;
-
-
interface ClickListener extends EventListener{
-
-
void click(ClickEvent clickEvent);
-
-
}
-
-
-
interface DblClickListener extends EventListener{
-
-
void dblClick(DblClickEvent dblClickEvent);
-
-
}
-
-
-
interface MouseMoveListener extends EventListener{
-
-
void mouseMove(MouseMoveEvent mouseMoveEvent);
-
-
}
三種監聽器分別監聽點選,雙擊和滑鼠移動。下面給出我們最重要的類,Button。
-
-
public class Button {
-
-
private String id;
-
private String value;
-
private ClickListener onclick;
-
private DblClickListener onDblClick;
-
private MouseMoveListener onMouseMove;
-
-
-
public void click(){
-
onclick.click(new ClickEvent(this));
-
}
-
-
public void dblClick(){
-
onDblClick.dblClick(new DblClickEvent(this));
-
}
-
-
public void mouseMove(int x,int y){
-
onMouseMove.mouseMove(new MouseMoveEvent(this,x,y));
-
}
-
-
public void setId(String id) {
-
this.id = id;
-
}
-
-
public void setValue(String value) {
-
this.value = value;
-
}
-
-
public void setOnclick(ClickListener onclick) {
-
this.onclick = onclick;
-
}
-
-
public void setOnDblClick(DblClickListener onDblClick) {
-
this.onDblClick = onDblClick;
-
}
-
-
public void setOnMouseMove(MouseMoveListener onMouseMove) {
-
this.onMouseMove = onMouseMove;
-
}
-
-
public String getId() {
-
return id;
-
}
-
-
public String getValue() {
-
return value;
-
}
-
-
public ClickListener getOnclick() {
-
return onclick;
-
}
-
-
public DblClickListener getOnDblClick() {
-
return onDblClick;
-
}
-
-
public MouseMoveListener getOnMouseMove() {
-
return onMouseMove;
-
}
-
-
}
可以看到,按鈕Button類有很多屬性,都是我們經常看到的,id,value,onclick等等。下面我們模擬編寫一個頁面,這個頁面可以當做是一個JSP頁面,我們只有一個按鈕,我們用JAVA語言把它描述出來,如下。
-
-
-
public class ButtonJsp {
-
-
private Button button;
-
-
public ButtonJsp() {
-
super();
-
button = new Button();
-
button.setId("submitButton");
-
button.setValue("提交");
-
button.setOnclick(new ClickListener() {
-
-
public void click(ClickEvent clickEvent) {
-
System.out.println("--------單擊事件程式碼---------");
-
System.out.println("if('表單合法'){");
-
System.out.println("\t表單提交");
-
System.out.println("}else{");
-
System.out.println("\treturn false");
-
System.out.println("}");
-
}
-
});
-
button.setOnDblClick(new DblClickListener() {
-
-
public void dblClick(DblClickEvent dblClickEvent) {
-
System.out.println("--------雙擊事件程式碼---------");
-
System.out.println("alert('您不能雙擊"+dblClickEvent.getButton().getValue()+"按鈕')");
-
}
-
});
-
button.setOnMouseMove(new MouseMoveListener() {
-
-
-
public void mouseMove(MouseMoveEvent mouseMoveEvent) {
-
System.out.println("--------滑鼠移動程式碼---------");
-
System.out.println("alert('您當前滑鼠的位置,x座標為:"+mouseMoveEvent.getX()+",y座標為:"+mouseMoveEvent.getY()+"')");
-
}
-
});
-
}
-
-
public Button getButton() {
-
return button;
-
}
-
-
}
以上可以認為我們給web服務中寫了一個簡單的頁面,下面我們看客戶在訪問我們的頁面時,我們的頁面在做什麼。
-
public class Client {
-
-
public static void main(String[] args) {
-
ButtonJsp jsp = new ButtonJsp();
-
-
jsp.getButton().dblClick();
-
jsp.getButton().mouseMove(10, 100);
-
jsp.getButton().mouseMove(15, 90);
-
jsp.getButton().click();
-
}
-
}
我們看執行結果可以看到,我們的三個事件都起了作用,最終提交了表單。
以上就是模擬整個JSP頁面中,我們的按鈕響應使用者事件的過程,我相信通過這兩個例子,各位應該對觀察者模式和事件驅動都有了自己的理解和認識,二者都是用來處理變化與響應的問題,其中觀察者更多的是釋出-訂閱,也就是類似讀者和作者的關係,而事件驅動更多的是為了響應客戶的請求,從而制定一系列的事件和監聽器,去處理客戶的請求與操作。
二者其實都是有自己的弱項的,只有掌握了模式的弱項才能更好的使用,不是有句話叫“真正瞭解一個東西,不是知道它能幹什麼,而是知道它不能幹什麼。”嗎?
觀察者模式所欠缺的是設計上的問題,即觀察者和被觀察者是多對一的關係,那麼反過來的話,就無法支援了。
各位可以嘗試將二者位置互換達到這個效果,這算是設計模式的活用,很簡單,就是讓被觀察者做成一個介面,提供是否改變的方法,讓觀察者維護一個被觀察者的列表,另外開啟一個執行緒去不斷的測試各個被觀察者是否改變。由於本篇已經夠長,所以LZ不再詳細編寫,如果有哪位讀者有需要,可以在下方留言,LZ看到的話,如果有時間,會寫出來放到資源裡供各位下載。
觀察者模式還有一個缺點就是,每一個觀察者都要實現觀察者介面,才能新增到被觀察者的列表當中,假設一個觀察者已經存在,而且我們無法改變其程式碼,那麼就無法讓它成為一個觀察者了,不過這個我們依然可以使用介面卡模式解決。但是還有一個問題就不好解決了,就是假如我們很多類都是現成的,當被觀察者發生變化時,每一個觀察者都需要呼叫不同的方法,那麼觀察者模式就有點捉襟見肘的感覺了,我們必須適配每一個類去統一他們變化的方法名稱為update,這是一個很可怕的事情。
對於事件驅動就沒有這樣的問題,我們可以實現多個監聽器來達到監聽多個事件源的目的,但是它的缺點剛才已經說過了,在事件源或者事件增加時,監聽器和事件類通常情況下會成對增加,造成系統的複雜性增加,不過目前看來,事件驅動模型一般都比較穩定,所以這個問題並不太明顯,因為很少見到無限增加事件的情況發生。
還有一個缺點就是我們的事件源需要看準時機觸發自己的各個監聽器,這也從某種意義上增加了事件源的負擔,造成了類一定程度上的臃腫。
最後,LZ再總結下二者針對的業務場景概述。
觀察者模式:釋出(release)--訂閱(subscibe),變化(change)--更新(update)
事件驅動模型:請求(request)--響應(response),事件發生(occur)--事件處理(handle)
感謝各位的收看。