1. 程式人生 > >Java 設計模式(八)觀察者模式

Java 設計模式(八)觀察者模式

一、定義

觀察者模式定義了一個一對多的依賴關係,讓多個觀察者物件同時監聽同一個主題物件。當這個主題狀態發生改變時,會通知所有觀察者物件,讓它們自動更新自己。

二、類似場景

  • 聊天室程式的建立。伺服器建立好後,A、B、C三個客戶端連線好公開聊天。A向伺服器傳送資料,伺服器在將資料分別傳送給其他線上客戶。也就是說,每個客戶端需要更新伺服器端的資料。
  • 網站上,很多人訂閱了“Java主題”的新聞。當有這個主題新聞時,就會將這些新聞發給所有訂閱的人。
  • 大家在玩CS遊戲時,伺服器需要將每個人的方位變化發給所有的客戶。

      上面這些場景,我們都可以使用觀察者模式來處理。我們可以把多個訂閱者、客戶稱之為觀察者

    ;需要同步給多個訂閱者的資料封裝到對物件中,稱之為目標

三、模式結構

  • 抽象主題角色(Subject): 把所有對觀察者物件的引用儲存在一個集合中,每個抽象主題角色都可以有任意數量的觀察者。抽象主題提供一個介面,可以增加和刪除觀察者角色。一般用一個抽象類和介面來實現。
  • 具體主題角色(ConcreteSubject): 在具體主題內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色通常用一個子類實現。
  • 抽象觀察者角色(Observer): 為所有具體的觀察者定義一個介面,在得到主題的通知時更新自己。
  • 具體觀察者角色(ConcreteObserver): 該角色實現抽象觀察者角色所要求的更新介面,以便使本身的狀態與主題的狀態相協調。通常用一個子類實現。如果需要,具體觀察者角色可以儲存一個指向具體主題角色的引用。

類圖:

這裡寫圖片描述

程式碼示例:

import java.util.*;

//抽象觀察類
interface Observer {
    public String getName();
    public void setName(String name);
    public void help(); //宣告支援盟友方法
    public void beAttacked(AllyControlCenter acc); //宣告遭受攻擊方法
}

//戰隊成員類:具體觀察者類
class Player implements Observer {
    private String name;

    public
Player(String name) { this.name = name; } public void setName(String name) { this.name = name; } public String getName() { return this.name; } //支援盟友方法的實現 public void help() { System.out.println("堅持住," + this.name + "來救你!"); } //遭受攻擊方法的實現,當遭受攻擊時將呼叫戰隊控制中心類的通知方法notifyObserver()來通知盟友 public void beAttacked(AllyControlCenter acc) { System.out.println(this.name + "被攻擊!"); acc.notifyObserver(name); } } //戰隊控制中心類:目標類 abstract class AllyControlCenter { protected String allyName; //戰隊名稱 protected ArrayList<Observer> players = new ArrayList<Observer>(); //定義一個集合用於儲存戰隊成員 public void setAllyName(String allyName) { this.allyName = allyName; } public String getAllyName() { return this.allyName; } //註冊方法 public void join(Observer obs) { System.out.println(obs.getName() + "加入" + this.allyName + "戰隊!"); players.add(obs); } //登出方法 public void quit(Observer obs) { System.out.println(obs.getName() + "退出" + this.allyName + "戰隊!"); players.remove(obs); } //宣告抽象通知方法 public abstract void notifyObserver(String name); } //具體戰隊控制中心類:具體目標類 class ConcreteAllyControlCenter extends AllyControlCenter { public ConcreteAllyControlCenter(String allyName) { System.out.println(allyName + "戰隊組建成功!"); System.out.println("----------------------------"); this.allyName = allyName; } //實現通知方法 public void notifyObserver(String name) { System.out.println(this.allyName + "戰隊緊急通知,盟友" + name + "遭受敵人攻擊!"); //遍歷觀察者集合,呼叫每一個盟友(自己除外)的支援方法 for(Object obs : players) { if (!((Observer)obs).getName().equalsIgnoreCase(name)) { ((Observer)obs).help(); } } } }

編寫如下客戶端測試程式碼:

class Client {
    public static void main(String args[]) {
        //定義觀察目標物件
AllyControlCenter acc;
        acc = new ConcreteAllyControlCenter("金庸群俠");

        //定義四個觀察者物件
        Observer player1,player2,player3,player4;

        player1 = new Player("楊過");
        acc.join(player1);

        player2 = new Player("令狐沖");
        acc.join(player2);

        player3 = new Player("張無忌");
        acc.join(player3);

        player4 = new Player("段譽");
        acc.join(player4);

        //某成員遭受攻擊
        Player1.beAttacked(acc);
    }
}

編譯並執行程式,輸出結果如下:

金庸群俠戰隊組建成功!

—————————-

楊過加入金庸群俠戰隊!

令狐沖加入金庸群俠戰隊!

張無忌加入金庸群俠戰隊!

段譽加入金庸群俠戰隊!

楊過被攻擊!

金庸群俠戰隊緊急通知,盟友楊過遭受敵人攻擊!

堅持住,令狐沖來救你!

堅持住,張無忌來救你!

堅持住,段譽來救你!

在本例項中,實現了兩次物件之間的聯動,當一個遊戲玩家Player物件的beAttacked()方法被呼叫時,將呼叫AllyControlCenter的notifyObserver()方法來進行處理,而在notifyObserver()方法中又將呼叫其他Player物件的help()方法。Player的beAttacked()方法、AllyControlCenter的notifyObserver()方法以及Player的help()方法構成了一個聯動觸發鏈,執行順序如下所示:

Player.beAttacked() –> AllyControlCenter.notifyObserver() –>Player.help()

四、推模式與拉模式

  • 推模式:每次都會把通知以廣播的方式傳送給所有觀察者,所有觀察者只能被動接收, 推送的資訊通常是主題物件的全部或部分資料 。
  • 拉模式: 主題物件在通知觀察者的時候,只傳遞少量資訊。如果觀察者需要更具體的資訊,由觀察者主動到主題物件中獲取,相當於是觀察者從主題物件中拉資料。一般這種模型的實現中,會把主題物件自身通過update()方法傳遞給觀察者,這樣在觀察者需要獲取資料的時候,就可以通過這個引用來獲取了 。
  • 比較: 推模式是假定主題物件知道觀察者需要的資料;而拉模式是主題物件不知道觀察者具體需要什麼資料,沒有辦法的情況下,乾脆把自身傳遞給觀察者,讓觀察者自己去按需要取值。

五、Java自帶對觀察者模式的支援

JavaSE中提供了java.util.Observable和java.util.Observer來實現觀察者模式。

程式碼示例
具體目標物件:

public class ConcreteSubject extends Observable{
    private int state;

    public int getState() {
        return state;
    }
    public void setState(int state) {
        this.state = state;//目標物件狀態發生改變

        setChanged();//表示目標物件已經做了更改
        notifyObservers(state);//通知所有觀察者
    }
}

具體觀察者:

public class ConcreteObserver implements Observer{
    private int mystate;
    public void update(Observable o, Object arg) {
        mystate=((ConcreteSubject)o).getState();
        System.out.println("觀察者接收到的狀態:"+mystate);
    }
}  

客戶端:

public class Client {
    public static void main(String[] args) {
        //建立具體主題
        ConcreteSubject cs=new ConcreteSubject();

        //建立觀察者
        ConcreteObserver observer1=new ConcreteObserver();
        ConcreteObserver observer2=new ConcreteObserver();
        ConcreteObserver observer3=new ConcreteObserver();

        //將觀察者加入到目標物件觀察者集合
        cs.addObserver(observer1);
        cs.addObserver(observer2);
        cs.addObserver(observer3);

        //目標物件改變
        cs.setState(100);
    }
}

輸出結果:

這裡寫圖片描述