Java設計模式(三)——觀察者模式和監聽器


為了實現多個模塊之間的聯動,最好的方法是使用觀察者模式。網上介紹的資料也比較多,今天我就從另一個方面談談自己對觀察者模式的理解。從JDK提供的支持庫里,我們能夠找到四個對象:Observable、Observer、EventListener、EventObject。

先模擬一個后台處理過程:

import java.util.Observable;

public class Subject extends Observable {
    private int value;

    public int getValue() {
        return value;
    }

    private void progress() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            value = i;
            setChanged(); // 值發生改變
            notifyObservers(); // 調用所有注冊的觀察者
        }
    }
    
    public void onStart() {
        progress();
    }

}

稍微對上面的代碼做一些解釋,順便介紹一下Observable這個類:

顧名思義“能夠被觀察的對象”,用戶能夠繼承它並增加自己的定義,它提供了幾個方法。

addObserver(Observer o):注冊觀察者,這個方法的內部其實就是提供了一個隊列。將多有觀察者儲存在隊列中,當有事件發生的時候遍歷這個隊列。

hasChanged()setChanged():當事件發生時,需要調用setChanged()方法,此時hasChanged()返回true否則返回false。

notifyObservers()notifyObservers(Object arg):通知所有觀察者可以獲取各自需要的變量或只推送某個對象。

下面模擬一個進度條:

public class Progress implements Observer {
    private int value;

    @Override
    public void update(Observable o, Object arg) {
        value = ((Subject)o).getValue();
        System.out.print("#");
    }
    
    public static void main(String[] args) {
        Progress ui = new Progress();
        Subject subject = new Subject();
        subject.addObserver(ui);
        subject.onStart();
    }
}

進度條作為后台程序的觀察者需要實現update(Observable o, Object arg)方法。當Subject調用setChanged()后使用notifyObservers()方法會回調update。需要注意,notifyObservers方法隱式調用clearChanged(),因此如果用戶試圖在update方法中調用o.hasChanged()的時候,只會返回false。

我們再來看一個使用監聽器的例子。相對上面的代碼來說,實現自己的監聽器會稍微復雜一些。但是只要我們先想清楚邏輯就不會有什么難度。

監聽器模式將對象分為了三個模塊:Source(事件源)、ChangeEvent(事件)、StatusListener(監聽器)。事件源可能會觸發多個事件,針對不同的事件都可以定義獨立的監聽器,當事件源觸發事件的時候監聽器獲得通知並實現用戶自定義的業務邏輯。相比Observer和Observable的二元實現來說。監聽器抽象了事件對象,目的是不同的事件源可能包含相同的觸發事件,為了提供更好的內聚處理。監聽器的處理邏輯是針對事件本身而言的。

那么也許你會好奇,如果監聽的對象是事件本身如何根據不同的事件源提供不同的邏輯呢?秘訣在於事件本身會提供一個事件源對象供監聽器判斷。

或許現在你反而會更加困惑。不要緊其實當我第一次自己去實現監聽器的時候面對這三個模塊的邏輯也相當混亂,等你看完代碼以后再回來仔細推敲上面的文字會豁然開朗。

先看一下事件源:

public class Source {
    private List<StatusListener> statusListeners = new ArrayList<>();
    
    public void addStatusListener(StatusListener listener) {
        statusListeners.add(listener);
    }
    
    public void onClick() {
        ChangeEvent event = new ChangeEvent(this);
        event.setStatus("click");
        notifyListener(event);
    }
    
    public void onDoubleClick() {
        ChangeEvent event = new ChangeEvent(this);
        event.setStatus("doubleClick");
        notifyListener(event);
    }
    
    public void onMove() {
        ChangeEvent event = new ChangeEvent(this);
        event.setStatus("move");
        notifyListener(event);
    }
    
    private void notifyListener(ChangeEvent event) {
        Iterator<StatusListener> it = statusListeners.iterator();
        while(it.hasNext()) {
            StatusListener listener = it.next();
            listener.changeStatus(event);
        }
    }
}

事件源提供了三個觸發事件,分別是點擊(click)、雙擊(doubleclick)、拖拽(move)。這是設計UI的時候經常遇到的三個事件,相信大家不應該陌生。Source沒有繼承任何接口或父類,完全是一個自定義的類。那么針對三種觸發類型,我們接下來需要提供事件。

public class ChangeEvent extends EventObject {
    private String status;
    
    public ChangeEvent(Object source) {
        super(source);
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }
    
}

ChangeEvent對象繼承EventObject,必須提供一個注冊事件源的構造方法。然后在事件中,我們定義了一個字符串變量作為保存狀態的接口,更加復雜的邏輯原理也是一樣的。

定義好事件以后,我們再定義一個監聽器接口。

public interface StatusListener extends EventListener {
    void changeStatus(ChangeEvent event);
}

EventListener接口僅僅是一個標志性接口,內部沒有做任何處理。所有邏輯都由使用者自己實現。我們就定義一個changeStatus方法,要求提供ChangeEvent作為參數。

最后返回Source類,添加一個測試用的main方法

public static void main(String[] args) {
        Source source = new Source();
        source.addStatusListener(new StatusListener(){

            @Override
            public void changeStatus(ChangeEvent event) {
                System.out.println(event.getStatus());
            }
        });
        source.onClick();
        source.onDoubleClick();
        source.onMove();
    }

在main方法中我們能夠觀察到一些區別,首先,監聽器並沒有提供默認的addListener方法。我們需要自己創建一個保存所有監聽對象的隊列。

其次,也沒有提供notifyListener方法,為了觸發監聽器我們也需要自己實現遍歷隊列的邏輯。

最后說一下我對以上兩種模式區別的理解。

Observable和Observer屬於對象驅動或值驅動。例如進度條的例子,UI界面需要時刻觀察后台進度的變化從而動態更新自己。這里的關鍵詞是動態更新。

EventListener和EventObject屬於事件驅動或方法驅動。例如按鈕的例子,用戶造成了某個事件,立刻觸發后台程序的響應。這里的關鍵詞是響應。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM