Java設計模式10:觀察者模式

五月的倉頡發表於2015-10-25

觀察者模式

觀察者模式也叫作釋出-訂閱模式,也就是事件監聽機制。觀察者模式定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件,這個主題物件在狀態上發生變化時,會通知所有觀察者物件,使他們能夠自動更新自己

 

觀察者模式的結構

一個軟體系統常常要求在某一個物件狀態發生變化時,某些其他的物件作出相應的改變。能做到這一點的設計方案有很多,但是為了使系統能夠易於複用,應該選擇低耦合度的設計方案。減少物件之間的耦合有利於系統的複用,但是同時需要使這些低耦合度的物件之間能夠維持行動的協調一致,保證高度的協作。觀察者模式是滿足這一要求的各種設計方案中最重要的一種。

觀察者模式所涉及的角色有:

1、抽象主題角色

抽象主題角色把所有對觀察者物件的引用儲存在一個集合中,每個主題都可以有任意數量的觀察者。抽象主題提供一個介面,可以增加和刪除觀察者物件。

2、具體主題角色

將有關狀態存入具體觀察者物件,在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。

3、抽象觀察者角色

為所有的具體觀察者提供一個介面,在得到主題通知時更新自己

4、具體觀察者角色

儲存與主題的狀態相關的狀態。具體觀察者角色實現抽象觀察者角色所要求的更新介面,以便使本身的狀態與主題的狀態協調

 

觀察者模式例項

抽象主題角色,有增加觀察者、刪除觀察者、通知觀察者的功能:

public abstract class Subject
{
    /** 用來儲存註冊的觀察者物件 */
    private List<Observer> list = new ArrayList<Observer>();
    
    /** 註冊觀察者物件 */
    public void attch(Observer observer)
    {
        list.add(observer);
        System.out.println("Attached an observer");
    }
    
    /** 刪除觀察者物件 */
    public void detach(Observer observer)
    {
        list.remove(observer);
        System.out.println("Detached an observer");
    }
    
    /** 通知所有註冊的觀察者物件 */
    public void notifyObservers(String newState)
    {
        for (int i = 0; i < list.size(); i++)
        {
            list.get(i).update(newState);
        }
    }
}

具體主題角色,這個change方法放在子類中是因為可能不同的主題在改變觀察者狀態的時候會做一些不同的操作,因此就不統一放在父類Subject裡面了:

public class ConcreteSubject extends Subject
{
    private String state;
    
    public String getState()
    {
        return state;
    }
    
    public void change(String newState)
    {
        state = newState;
        System.out.println("主題狀態為:" + state);
        
        // 狀態發生改變時,通知各個觀察者
        this.notifyObservers(state);
    }
}

觀察者介面:

public interface Observer
{
    void update(String state);
}

具體觀察者實現了觀察者介面:

public class ConcreteObserver implements Observer
{
    /** 觀察者的狀態 */
    private String observerState;
    
    public void update(String state)
    {
        /** 更新觀察者的狀態 */
        observerState = state;
        System.out.println("狀態為:" + observerState);
    }
}

客戶端呼叫程式碼,一旦主題呼叫了change方法改變觀察者的狀態,那麼觀察者Observer裡面的observerState全都改變了:

public static void main(String[] args)
{
    /** 建立主題角色 */
    ConcreteSubject subject = new ConcreteSubject();
    
    /** 建立觀察者物件 */
    Observer observer = new ConcreteObserver();
    
    /** 將觀察者註冊到主題物件上 */
    subject.attch(observer);
    
    /** 改變主題物件的狀態 */
    subject.change("new state");
}

執行結果為:

Attached an observer
主題狀態為:new state
狀態為:new state

這裡只新增了一個觀察者,有興趣的可以試試看多新增幾個觀察者,效果都是一樣的,主題角色改變狀態,觀察者狀態全變。

 

觀察者模式的兩種模型

1、推模型

主題物件向觀察者推送主題的詳細資訊,不管觀察者是否需要。推送的資訊通常是主題物件的全部或部分資料,上面的例子就是典型的推模型

2、拉模型

主題物件在通知觀察者的時候,只傳遞少量資訊。如果觀察者需要更具體的資訊,由觀察者主動到主題物件中去獲取,相當於是觀察者從主題物件中拉資料。一般這種模型的實現中,會把主題物件自身通過update()方法傳遞給觀察者,這樣觀察者在需要獲取資料的時候,就可以通過這個引用來獲取了。

 

兩種模型的比較

1、推模型是假設主題物件知道觀察者需要的資料,拉模型是假設主題物件不知道觀察者需要什麼資料,乾脆把自身傳遞過去,讓觀察者自己按需要取值

2、推模型可能會使得觀察者物件難以複用,因為觀察者的update()方法是按需要定義的引數,可能無法兼顧到沒有考慮到的使用情況,這意味著出現新的情況時,可能要提供新的update()方法

 

觀察者模式在Java中的應用及解讀

JDK是有直接支援觀察者模式的,就是java.util.Observer這個介面:

public interface Observer {
    /**
     * This method is called whenever the observed object is changed. An
     * application calls an <tt>Observable</tt> object's
     * <code>notifyObservers</code> method to have all the object's
     * observers notified of the change.
     *
     * @param   o     the observable object.
     * @param   arg   an argument passed to the <code>notifyObservers</code>
     *                 method.
     */
    void update(Observable o, Object arg);
}

這就是觀察者的介面,定義的觀察者只需要實現這個介面就可以了。update()方法,被觀察者物件的狀態發生變化時,被觀察者的notifyObservers()方法就會呼叫這個方法:

public class Observable {
    private boolean changed = false;
    private Vector obs;
   
    /** Construct an Observable with zero Observers. */

    public Observable() {
    obs = new Vector();
    }

    /**
     * Adds an observer to the set of observers for this object, provided 
     * that it is not the same as some observer already in the set. 
     * The order in which notifications will be delivered to multiple 
     * observers is not specified. See the class comment.
     *
     * @param   o   an observer to be added.
     * @throws NullPointerException   if the parameter o is null.
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
    if (!obs.contains(o)) {
        obs.addElement(o);
    }
    }
    ...
}

這是被觀察者的父類,也就是主題物件。這是一個執行緒安全的類,是基於Vector實現的。主題物件中有這些方法對觀察者進行操作:

方  法 作  用
addObserver(Observer o) 如果觀察者與集合中已有的觀察者不同,則向物件的觀察者集合中新增此觀察者
clearChanged()、hasChanged()、setChanged() 這三個方法算是一對,用來標記此觀察者物件(主題物件)是否被改變的狀態的
countObservers() 返回觀察者物件的數目
deleteObserver(Observer o) 從物件的觀察者集合中刪除某個觀察者
deleteObservers() 清除觀察者列表
notifyObservers()、notifyObservers(Object arg) 如果本物件有變化則通知所有等級的觀察者,呼叫update()方法

 

利用JDK支援的主題/觀察者的例子

建立一個觀察者:

public class Watched extends Observable
{
    private String data = "";
    
    public String getData()
    {
        return data;
    }
    
    public void setData(String data)
    {
        if (!this.data.equals(data))
        {
            this.data = data;
            setChanged();
        }
        notifyObservers();
    }
}

建立一個主題:

public class Watcher implements Observer
{
    String data;

    public Watcher(Observable o)
    {
        o.addObserver(this);
    }
    
    public String getData()
    {
        return data;
    }
    
    public void update(Observable o, Object arg)
    {
        this.data = ((Watched)o).getData();
        System.out.println("狀態發生改變:" + ((Watched)o).getData());
    }
}

寫一個main函式呼叫一下:

public static void main(String[] args)
{
    /** 建立被觀察者物件 */
    Watched watched = new Watched();
    
    /** 建立觀察者物件,並將被觀察者物件登記 */
    Watcher watcher = new Watcher(watched);
    
    /** 給被觀察者狀態賦值 */
    watched.setData("start");
    watched.setData("run");
    watched.setData("stop");
}

執行結果為:

狀態發生改變:start
狀態發生改變:run
狀態發生改變:stop

看到主題物件改變的時候,觀察者物件的狀態也隨之改變

 

觀察者模式的優點以及實際應用

引入設計模式最主要的作用我認為就是兩點:

1、去重複程式碼,使得程式碼更清晰、更易讀、更易擴充套件

2、解耦,使得程式碼可維護性更好,修改程式碼的時候可以儘量少改地方

使用觀察者模式可以很好地做到這兩點。增加觀察者,直接new出觀察者並註冊到主題物件之後就完事了,刪除觀察者,主題物件呼叫方法刪除一下就好了,其餘都不用管。主題物件狀態改變,內部會自動幫我們通知每一個觀察者,是不是很方便呢?

觀察者模式主要應用場景有:

1、對一個物件狀態的更新需要其他物件同步更新

2、物件僅需要將自己的更新通知給其他物件而不需要知道其他物件的細節,如訊息推送

 

相關文章