從消息機制談到觀察者模式

從簡單的例子開始

一樣,咱們仍是先看一個簡單例子:建立一個窗口實現加法的計算功能。其效果以下:java


這裏寫圖片描述
圖1: 加法計算編程

 

Calculator.java:安全

import javax.swing.*;
import javax.swing.border.BevelBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * Created with IntelliJ IDEA.
 * User: luoweifu
 * Date: 15-5-5
 * Time: 下午9:14
 * To change this template use File | Settings | File Templates.
 */
public class Calculator {
    /**
     * 主窗口的寬度
     */
    public static final int WIDTH = 500;
    /**
     * 主窗口的高度
     */
    public static final int HEIGHT = 100;

    private JFrame frameCalculator;
    private JEditorPane editAddend1;
    private JEditorPane editAddend2;
    private JEditorPane editResult;
    private JLabel labelPlus;
    private JButton btEqual;
    public Calculator() {
        frameCalculator = new JFrame();
    }

    public void launchFrame() {
        frameCalculator.setSize(WIDTH, HEIGHT);
        frameCalculator.setLocationRelativeTo(null);
        frameCalculator.setTitle(加法計算);

        Container container = frameCalculator.getContentPane();
        container.setLayout(new FlowLayout(FlowLayout.CENTER, 10, 10));
        editAddend1 = new JEditorPane();
        editAddend1.setBorder(new BevelBorder(BevelBorder.LOWERED));
        editAddend2 = new JEditorPane();
        editAddend2.setBorder(new BevelBorder(BevelBorder.LOWERED));
        labelPlus = new JLabel(+);
        btEqual = new JButton(=);
        editResult = new JEditorPane();
        editResult.setBorder(new BevelBorder(BevelBorder.LOWERED));
        editResult.setEditable(false);
        container.add(editAddend1);
        container.add(labelPlus);
        container.add(editAddend2);
        container.add(btEqual);
        container.add(editResult);
        frameCalculator.setVisible(true);
        //frameCalculator.setDefaultCloseOperation(EXIT_ON_CLOSE);

        class AdditionCalculate implements ActionListener {
            @Override
            public void actionPerformed(ActionEvent e) {
                int add1 = Integer.parseInt(editAddend1.getText());
                int add2 = Integer.parseInt(editAddend2.getText());
                int result = add1 + add2;
                editResult.setText(result + );
            }
        }

        AdditionCalculate additionCalculate = new AdditionCalculate();
        btEqual.addActionListener(additionCalculate);
    }

    public static void main(String args[]) {
        Calculator calculator = new Calculator();
        calculator.launchFrame();
    }
}

上面這個例子中,窗口和全部的控件建立完成以後,btEqual按鈕邦定了一個監聽對象additionCalculate,一旦這個按鈕被點擊, 就會通知additionCalculate對象,additionCalculate對象監聽到點擊事件,就會調用actionPerformed方法 做出相應的響應。additionCalculate是內部類AdditionCalculate的對象,AdditionCalculate實現了 ActionListener 接口。
經過上面的例子,你也許看出來了Java Swing/AWT包中窗口、控件的響應方式是一種源-監聽器(Source/Listener)模式,也叫作觀察者模式,這種機制常稱爲事件機制。服務器

事件機制與消息機制的區別

Windows API能夠開發窗口(界面)程序,Java經過Swing/AWT包也能夠開發窗口(界面)程序,那麼他們之間有什麼異同呢?
1. 實現方式不一樣,Windows API主要是經過回調,提供對外的接口由用戶去實現對應的處理,內部由操做系統實現,咱們看不到;Java中的Swing/AWT主要源-監聽器(觀察者)模式,實現窗口(控件)對象與事件處理對象的邦定。
2. Windows API的消息機制有一個消息循環一直在接收消息,它是線程阻塞的。而Java的的Swing/AWT是一個通知方式,只有窗口(控件)有變化(被鼠標、鍵盤等觸發)時纔會通知監聽者去處理,是非阻塞的。
3. 相同點:都有消息源——窗口(控件),都有消息處理,Windows API是窗口處理函數,Java中是監聽者的處理方法,都有消息(Java叫事件Event)。若是把Windows API中消息隊列和消息循環去掉,二者就很像了,就如同使用SendMessage直接把消息發送到窗口處理函數。因此,事件機制也能夠認爲是特殊的消息 機制。網絡

既然Java中的窗口程序是經過源-監聽器(觀察者)模式實現的,咱們就有必要討論一下觀察者模式了。ide


觀察者模式

觀察者模式,顧名思意就是觀察與被觀察的關係,好比你在燒開水得時時看着它開沒開,你就是觀察者,開水就是被觀察者;再好比說你在帶小孩,你關注她 是否是餓了,是否是喝了,是否是撒尿了,你就是觀察者,小孩就被觀察者。觀察者模式是對象的行爲模式,又叫發佈-訂閱 (Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者 (Dependents)模式。當你看這些模式的時候,不要以爲陌生,它們就是觀察者模式。函數

觀察者模式通常是一種一對多的關係,能夠有任意個(一個或多個)觀察者對象同時監聽某一個對象。監聽的對象叫觀察者(後面提到監聽者,其實就指觀察者,二者是等價的),被監聽的對象叫被觀察者(Observable,也叫主題Subject)。被觀察者對象在狀態上發生變化時,會通知全部觀察者對象,使它們可以作出相應的變化(如自動更新本身的信息)。
咱們就以上面提到的燒開水的一個簡單生活實例來模擬一下觀察者模式。
代碼ObserverModule.java:this

//人,觀察者
class Person {
    public void update(String data) {
        System.out.println(data + 關電源...);
    }
}

//水,被觀察者
class Water {
    private Person person;
    private boolean isBoiled;
    public Water() {
        isBoiled = false;
    }

    public void SetBoiled() {
        isBoiled = true;
        notifyObserve();
    }
    public void addObserver(Person person) {
        this.person = person;
    }

    public void removeObserver() {
        if (person != null) {
            person = null;
        }
    }

    public void notifyObserve() {
        if (isBoiled && person != null) {
            person.update(水開了,);
            isBoiled = false;
        }
    }
}

//客戶端
public class ObserverModule {
    public static void main(String args[]) {
        Person person = new Person();
        Water water = new Water();
        water.addObserver(person);
        water.SetBoiled();
    }
}

結果以下:spa

水開了,關電源…操作系統

這個代碼很是簡單,水開了就會通知人,人就去關電源。但也有一個問題,就是拓展性很差,不靈活。若是咱們燒的開水不是用來喝,而用來洗澡,我就要監 測它的溫度,可能50度就關電源,也可能要60度才行,這樣一個監聽就不夠了,還監聽溫度的隨時變化;再好比水開了以後,我不是關電源,而是讓它保溫。你 的updae又得改了……
因此上面這個代碼拓展行是很差,但已經實現了咱們的基本想法,咱們算是咱們的第一個版本(版本)。接下來咱們再看一下,升級版:
版本2:ObserverModule.java

//觀察者
interface Observer {
    public void update(Observable observable);
}

//被觀察者
abstract class  Observable {
    protected boolean isChanaged;
    protected List observers = new ArrayList();

    public Observable() {
        isChanaged = false;
    }
    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    public void removeObservers() {
        observers.clear();
    }
    public void notifyObservers() {
        if (isChanaged) {
            for (int i = 0; i < observers.size(); i ++) {
                observers.get(i).update(this);
            }
            isChanaged = false;
        }
    }
}

//人,溫度監測
class TemperatureObserver implements Observer{
    @Override
    public void update(Observable observable) {
        Water water = (Water)observable;
        System.out.println(溫度: + water.getTemperature() +      狀態: + water.getStatus());
        System.out.println(TemperatureObserver observing...);
    }
}

class BoildObserver implements Observer {
    String doSomthing;
    BoildObserver(String doSomthing) {
        this.doSomthing = doSomthing;
    }

    @Override
    public void update(Observable observable) {
        Water water = (Water)observable;
        if (water.getTemperature() >= 100) {
            System.out.println(狀態: + water.getStatus());
            System.out.println(BoildObserver: + doSomthing);
        }

    }
}
//水,被觀察者
class Water extends Observable{
    private double temperature;
    private String status;

    public Water() {
        super();
        this.temperature = 0;
        this.status = 冷水;
    }

    public Water(Observer observer) {
        this();
        observers.add(observer);
    }

    public double getTemperature() {
        return temperature;
    }

    public String getStatus() {
        return status;
    }

    public void change(double temperature) {
        this.temperature = temperature;
        if (temperature < 40) {
            status = 冷水;
        } else if (temperature >= 40 && temperature < 60) {
            status = 溫水;
        }else if (temperature >= 60 && temperature < 100 ) {
            status = 熱水;
        } else {
            status = 開水;
        }
        this.isChanaged = true;
        notifyObservers();
    }
}

//客戶端
public class ObserverModule {
    public static void main(String args[]) {
        TemperatureObserver temperatureObserver = new TemperatureObserver();
        BoildObserver boildObserver1 = new BoildObserver(關閉電源...);
        BoildObserver boildObserver2 = new BoildObserver(繼續保溼...);
        Water water = new Water(temperatureObserver);
        water.addObserver(boildObserver1);
        water.addObserver(boildObserver2);
        water.change(45);
        water.change(80);
        water.change(100);
    }
}

結果以下:

溫度:45.0 狀態:溫水
TemperatureObserver observing…
溫度:80.0 狀態:熱水
TemperatureObserver observing…
溫度:100.0 狀態:開水
TemperatureObserver observing…
狀態:開水
BoildObserver:關閉電源…
狀態:開水
BoildObserver:繼續保溼…

觀察者模式設計:

經過上面這個活生生的例子,咱們總結一下觀察者模式的設計。
觀察者模式的類結構關係以下:

這裏寫圖片描述
觀察者模式的類圖結構

 

在設計觀察者模式的程序時要注意如下幾點:
1. 要明確誰是觀察者誰是被觀察者,只要明白誰是關注對象,問題也就明白了。通常觀察者與被觀察者之間的是多對一的關係,一個被觀察對象能夠有多個監聽對象(觀察者)。如一個編輯框,有鼠標點擊的監聽者,也有鍵盤的監聽者,還有內容改變的監聽者。
2. Observable在發送廣播通知的時候,無須指定具體的Observer,Observer能夠本身決定是否要訂閱Subject的通知。
3. 被觀察者至少須要有三個方法:添加監聽者、移除監聽者、通知Observer的方法;觀察者至少要有一個方法:更新方法,更新當前的內容,做出相應的處理。
注:添加監聽者、移除監聽者在不一樣的模型中可能會有不一樣命名,如觀察者模型中通常,addObserver、removeObserver;在源-監聽器 (Source/Listener)模型中通常是attach/detach,應用在桌面編程的窗口中,還多是attachWindow /detachWindow,或Register/UnRegister。不要被名稱迷糊了,無論他們是什麼名稱,其實功能都是同樣的,就是添加/刪除觀 察者。
4. 觀察者模式的應用場景: <1>.對一個對象狀態的更新須要其餘對象同步更新;,或者一個對象的更新須要依賴另外一個對象的更新;<2>.對象僅須要將本身的更新通知給其餘對象而不須要知道其餘對象的細節,如消息推送。

推模型和拉模型

觀察者模式根據其側重的功能還能夠分爲推模型和拉模式
推模型:被觀察者對象向觀察者推送主題的詳細信息,無論觀察者是否須要,推送的信息一般是主題對象的所有或部分數據。通常這種模型的實現中,會把被觀察者對象中的所有或部分信息經過update的參數傳遞給觀察者[update(Object obj) ]。
拉模型:被觀察者在通知觀察者的時候,只傳遞少許信息。若是觀察者須要更具體的信息,由觀察者主動到被觀察者對象中獲 取,至關因而觀察者從被觀察者對象中拉數據。通常這種模型的實現中,會把被觀察者對象自身經過update方法傳遞給觀察者 [update(Observable observable ) ],這樣在觀察者須要獲取數據的時候,就能夠經過這個引用來獲取了。

JDK對觀察者模式的支持

其實JDK已經提供了對觀察者模式接口的定義了。在java.util庫裏面,提供了一個Observable類以及一個Observer接口,構成JAVA語言對觀察者模式的支持。咱們能夠看一下Java中的源碼
Observable接口:

package java.util;

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();
    }
}

Observer接口:

package java.util;

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

經過前面的分析,再來看Java的源碼,相信不會太難了。這裏有個比較好的地方是Observable類中的addObserver、deleteObserver、notifyObservers等方法已經幫咱們考慮了線程同步的問題,這樣更安全。


迴歸本質

咱們再回顧一下加法計算器的例子。經過觀察者模式的分析,也許你已經清楚了,AdditionCalculate的對象 additionCalculate就是觀察者;JButton的對象btEqual就是被觀察者,同時也是消息 源;btEqual.addActionListener(additionCalculate);就是添加監聽者。ActionListener中的 public void actionPerformed(ActionEvent e)就至關於update方法,只不過參數e消息源產生的消息(事件)。

觀察者模式還能夠用於網絡中的客戶端和服務器,好比手機中的各類App的消息推送,服務端是觀察者,各個手機App是被觀察者,一旦服務器上的數據(如App升級信息)有更新,就會被推送到手機客戶端。

相關文章
相關標籤/搜索