「補課」進行時:設計模式(15)——觀察者模式

1. 前文匯總

「補課」進行時:設計模式系列

2. 觀察者模式

2.1 定義

觀察者模式(Observer Pattern)也叫做發佈訂閱模式(Publish/subscribe),它是一個在項目中經常使用的模式,其定義如下:

Define a one-to-many dependency between objects so that when oneobject changes state,all its dependents are notified and updatedautomatically.(定義對象間一種一對多的依賴關係,使得每當一個對象改變狀態,則所有依賴於它的對象都會得到通知並被自動更新。)

2.2 通用類圖

  • Subject 被觀察者: 定義被觀察者必須實現的職責,它必須能夠動態地增加、取消觀察者。它一般是抽象類或者是實現類,僅僅完成作為被觀察者必須實現的職責:管理觀察者並通知觀察者。
  • ConcreteSubject 具體的被觀察者: 定義被觀察者自己的業務邏輯,同時定義對哪些事件進行通知。
  • Observer 觀察者: 觀察者接收到消息後,即進行update(更新方法)操作,對接收到的信息進行處理。
  • ConcreteObserver 具體的觀察者: 每個觀察在接收到消息後的處理反應是不同,各個觀察者有自己的處理邏輯。

2.3 通用代碼

Subject 被觀察者:

public abstract class Subject {
    // 定義一個觀察者數組
    private Vector<Observer> obsVector = new Vector<>();
    // 添加一個觀察者
    public void addObserver(Observer obsVector) {
        this.obsVector.add(obsVector);
    }
    // 刪除一個觀察者
    public void delObserver(Observer observer) {
        this.obsVector.remove(observer);
    }
    // 通知所有觀察者
    public void notifyObservers() {
        for (Observer obs : this.obsVector) {
            obs.update();
        }
    }
}

ConcreteSubject 具體的被觀察者:

public class ConcreteSubject extends Subject {
    public void doSomething() {
        // 具體的業務
        super.notifyObservers();
    }
}

Observer 觀察者:

public interface Observer {
    void update();
}

ConcreteObserver 具體的觀察者:

public class ConcreteObserver implements Observer{
    @Override
    public void update() {
        System.out.println("進行消息處理");
    }
}

測試場景類:

public class Test {
    public static void main(String[] args) {
        // 創建一個被觀察者
        ConcreteSubject subject = new ConcreteSubject();
        // 創建一個觀察者
        Observer observer = new ConcreteObserver();
        // 觀察者觀察被觀察者
        subject.addObserver(observer);
        // 觀察者開始活動了
        subject.doSomething();
    }
}

3. 一個案例

觀察者模式是設計模式中的超級模式,有關他的應用隨處可見。

就比如說微信公眾號,我每天推送一篇博文內容,訂閱的用戶都能夠在我發佈推送之後及時接收到推送,方便地在手機端進行閱讀。

訂閱者接口(觀察者)

public interface Subscriber {
    void receive(String publisher, String articleName);
}

微信客戶端(具體觀察者)

public class WeChatClient implements Subscriber {

    private String username;

    public WeChatClient(String username) {
        this.username = username;
    }

    @Override
    public void receive(String publisher, String articleName) {
        System.out.println(String.format("用戶<%s> 接收到 <%s>微信公眾號 的推送,文章標題為 <%s>", username, publisher, articleName));
    }
}

一個微信客戶端(具體觀察者)

public class Publisher {
    private List<Subscriber> subscribers;
    private boolean pubStatus = false;

    public Publisher() {
        subscribers = new ArrayList<>();
    }

    protected void subscribe(Subscriber subscriber) {
        this.subscribers.add(subscriber);
    }

    protected void unsubscribe(Subscriber subscriber) {
        if (this.subscribers.contains(subscriber)) {
            this.subscribers.remove(subscriber);
        }
    }

    protected void notifySubscribers(String publisher, String articleName) {
        if (this.pubStatus == false) {
            return;
        }
        for (Subscriber subscriber : this.subscribers) {
            subscriber.receive(publisher, articleName);
        }
        this.clearPubStatus();
    }

    protected void setPubStatus() {
        this.pubStatus = true;
    }

    protected void clearPubStatus() {
        this.pubStatus = false;
    }
}

具體目標

public class WeChatAccounts extends Publisher {
    private String name;

    public WeChatAccounts(String name) {
        this.name = name;
    }

    public void publishArticles(String articleName, String content) {
        System.out.println(String.format("\n<%s>微信公眾號 發佈了一篇推送,文章名稱為 <%s>,內容為 <%s> ", this.name, articleName, content));
        setPubStatus();
        notifySubscribers(this.name, articleName);
    }
}

測試類

public class Test {
    public static void main(String[] args) {
        WeChatAccounts accounts = new WeChatAccounts("極客挖掘機");

        WeChatClient user1 = new WeChatClient("張三");
        WeChatClient user2 = new WeChatClient("李四");
        WeChatClient user3 = new WeChatClient("王五");

        accounts.subscribe(user1);
        accounts.subscribe(user2);
        accounts.subscribe(user3);

        accounts.publishArticles("設計模式 | 觀察者模式及典型應用", "觀察者模式的內容...");

        accounts.unsubscribe(user1);
        accounts.publishArticles("設計模式 | 單例模式及典型應用", "單例模式的內容....");
    }
}

測試結果

<極客挖掘機>微信公眾號 發佈了一篇推送,文章名稱為 <設計模式 | 觀察者模式及典型應用>,內容為 <觀察者模式的內容...> 
用戶<張三> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 觀察者模式及典型應用>
用戶<李四> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 觀察者模式及典型應用>
用戶<王五> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 觀察者模式及典型應用>

<極客挖掘機>微信公眾號 發佈了一篇推送,文章名稱為 <設計模式 | 單例模式及典型應用>,內容為 <單例模式的內容....> 
用戶<李四> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 單例模式及典型應用>
用戶<王五> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 單例模式及典型應用>

4. JDK 對的觀察者模式的支持

觀察者模式在 Java 語言中的地位非常重要。在 JDK 的 java.util 包中,提供了 Observable 類以及 Observer 接口,它們構成了JDK對觀察者模式的支持。

java.util.Observer 接口中,僅有一個 update(Observable o, Object arg) 方法,當觀察目標發生變化時被調用:

public interface Observer {
    void update(Observable o, Object arg);
}

java.util.Observable 類則為目標類:

public class Observable {
    private boolean changed = false;
    private Vector<Observer> 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);
    }
    // 通知方法,用於在方法內部循環調用向量中每一個觀察者的update()方法
    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();
    }
    // 該方法被調用後會設置一個boolean類型的內部標記變量changed的值為true,表示觀察目標對象的狀態發生了變化
    protected synchronized void setChanged() {
        changed = true;
    }
    // 用於將changed變量的值設為false,表示對象狀態不再發生改變或者已經通知了所有的觀察者對象,調用了它們的update()方法
    protected synchronized void clearChanged() {
        changed = false;
    }
    // 返回對象狀態是否改變
    public synchronized boolean hasChanged() {
        return changed;
    }
    // 返迴向量中觀察者的數量
    public synchronized int countObservers() {
        return obs.size();
    }
}

相比較我們自己的示例 Publisherjava.util.Observer 中多了並發和 NPE 方面的考慮 。

使用 JDK 對觀察者模式的支持,改寫一下上面的示例:

增加一個通知類 WechatNotice ,用於推送通知的傳遞:

public class WechatNotice {
    private String publisher;
    private String articleName;
    // 省略 get/set
}

然後改寫 WeChatClientWeChatAccounts ,分別實現 JDK 的 Observer 接口和繼承 Observable 類:

public class WeChatClient implements Observer {

    private String username;

    public WeChatClient(String username) {
        this.username = username;
    }

    @Override
    public void update(Observable o, Object arg) {
        WechatNotice notice = (WechatNotice) arg;
        System.out.println(String.format("用戶<%s> 接收到 <%s>微信公眾號 的推送,文章標題為 <%s>", username, notice.getPublisher(), notice.getArticleName()));
    }
}

public class WeChatAccounts extends Observable {
    private String name;

    public WeChatAccounts(String name) {
        this.name = name;
    }

    public void publishArticles(String articleName, String content) {
        System.out.println(String.format("\n<%s>微信公眾號 發佈了一篇推送,文章名稱為 <%s>,內容為 <%s> ", this.name, articleName, content));
        setChanged();
        notifyObservers(new WechatNotice(this.name, articleName));
    }
}

最後是一個測試類:

public class Test {
    public static void main(String[] args) {
        WeChatAccounts accounts = new WeChatAccounts("極客挖掘機");

        WeChatClient user1 = new WeChatClient("張三");
        WeChatClient user2 = new WeChatClient("李四");
        WeChatClient user3 = new WeChatClient("王五");

        accounts.addObserver(user1);
        accounts.addObserver(user2);
        accounts.addObserver(user3);

        accounts.publishArticles("設計模式 | 觀察者模式及典型應用", "觀察者模式的內容...");

        accounts.deleteObserver(user1);
        accounts.publishArticles("設計模式 | 單例模式及典型應用", "單例模式的內容....");
    }
}

測試結果:

<極客挖掘機>微信公眾號 發佈了一篇推送,文章名稱為 <設計模式 | 觀察者模式及典型應用>,內容為 <觀察者模式的內容...> 
用戶<王五> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 觀察者模式及典型應用>
用戶<李四> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 觀察者模式及典型應用>
用戶<張三> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 觀察者模式及典型應用>

<極客挖掘機>微信公眾號 發佈了一篇推送,文章名稱為 <設計模式 | 單例模式及典型應用>,內容為 <單例模式的內容....> 
用戶<王五> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 單例模式及典型應用>
用戶<李四> 接收到 <極客挖掘機>微信公眾號 的推送,文章標題為 <設計模式 | 單例模式及典型應用>

和前面的示例結果完全一致。