序
我想大家一定都去醫(yī)院掛過號,大概流程是:掛號柱告,到就診室門口排隊截驮,等著醫(yī)生叫號看病。今天說的觀察者模式就是這樣一個道理际度。很多的病人就是觀察者葵袭,等待醫(yī)生狀態(tài)發(fā)生改變——看完病,叫號乖菱。然后所有的病人聽到號坡锡,看看叫的是不是自己,走入診室看病窒所,或者繼續(xù)等待鹉勒。
1、背景敘述
依然感覺還是用書上的例子比較好吵取,但是像“氣象觀測站”這種東西禽额,除了看天氣預報,我絕不會想到皮官。為了不讓問題更加復雜脯倒,我想還是直接切入主題,用比較形式化的描述方式來闡述臣疑,也能更快進入主題盔憨。
2、觀察者模式
對于某個“對象A”讯沈,當它的狀態(tài)發(fā)生變化時——某個屬性發(fā)生變化郁岩,或者調用某個函數。則其他的對象也因為“對象A”的變化做出某些操作缺狠∥噬鳎“對象A”為被監(jiān)聽者,其他的對象為監(jiān)聽者挤茄。有沒有想到Java中的事件如叼?對,這個就是事件的原型穷劈。先結合訂報紙的示例笼恰,用語言描述一下具體實現踊沸,然后上代碼,最后給大家說下這樣做有啥好處社证。
1)訂報紙需要以下步驟
(1)去郵局逼龟,請求訂報紙,郵局將我們的個人信息注冊追葡∠俾桑——郵局就是“被監(jiān)聽者”,我們是“監(jiān)聽者”宜肉。監(jiān)聽者需要到被監(jiān)聽者那里注冊個人信息匀钧。
(2)當郵局到了新報紙,則就將報紙分發(fā)給我們谬返≈梗——郵局從沒有報紙的狀態(tài),轉換到有報紙的狀態(tài)遣铝,狀態(tài)發(fā)生改變吊圾,需要告訴我們狀態(tài)發(fā)生改變,“報紙”就是“被監(jiān)聽者”發(fā)送給“監(jiān)聽者”信息
(3)我們收到報紙后翰蠢,有的人讀報紙项乒,了解新聞;有的人比較土豪梁沧,用來包家具檀何;有的人更土豪,送給鄰居廷支∑导——“監(jiān)聽者”收到消息后,采取不同的動作恋拍。
從以上分析舉例來看垛孔,監(jiān)聽者模式(也就是觀察者模式,監(jiān)聽者說的比較順口)其實是一種通信模式施敢。就是將信息進行廣播周荐。然后收到相同信息的不同實體,根據信息和自身特點僵娃,做出相應操作概作。
需要注意的是,因為投遞員只認識綠皮郵筒默怨,所以所有的監(jiān)聽者要提供統(tǒng)一的監(jiān)聽接口來接受消息讯榕。監(jiān)聽接口其實就是個函數。
但是,為了保證程序面向對象的良好設計愚屁,我們使用接口來對各個類進行修飾济竹。
2)程序部分
import java.util.ArrayList;
//測試類
public class Test
{
public static void main(String args[])
{
PostOffice postOffice=new PostOffice();//被觀察者
Observer1 observer1=new Observer1();//觀察者1號
Observer2 observer2=new Observer2();//觀察者2號
postOffice.addObserver(observer1);//注冊觀察者1號
postOffice.addObserver(observer2);//注冊觀察者2號
postOffice.setState(true, true);//改變被觀察者狀態(tài)
}
}
/*
如上面的程序,監(jiān)聽者需要跑到被監(jiān)聽者那里去注冊霎槐,只有注冊了规辱,被監(jiān)聽者才能發(fā)消息給監(jiān)聽者。
*/
interface Subject //定義被監(jiān)聽者接口栽燕,但是有一個不好點,下面會仔細說改淑。
{
public void addObserver(Observer o);
public void deleteObserver(Observer o);
public void notifyObserver(Object otherArg);
}
interface Observer//定義監(jiān)聽者接口碍岔,實際只是定義一個接受信息的接口
{
public void receive(Subject subject, Object otherArg );
}
//實現被被觀察者接口
class PostOffice implements Subject
{
private boolean newsPaper=false;
private boolean gift=false;
private ArrayList<Observer> arrayList;//存放觀察者的列表
public PostOffice()//構造函數
{
this.newsPaper=false;
this.gift=false;
this.arrayList=new ArrayList<Observer>(); //注意一定要初始化對象實體
}
public void stateChanged()//用戶自定義
{
this.notifyObserver(null);
}
public void setState(boolean newspaper, boolean gift) //用戶自定義
{
this.newsPaper=newspaper;
this.gift=gift;
this.stateChanged();
}
//以下為實現的Subject接口
public void addObserver(Observer o)
{
this.arrayList.add(o);
}
public void deleteObserver(Observer o)
{
int index=this.arrayList.indexOf(o);
if(index>0)
this.arrayList.remove(index);
}
public void notifyObserver(Object otherArg)
{
for (Observer observer : this.arrayList)
observer.receive(this, otherArg);
}
}
class Observer1 implements Observer
{
@Override
public void receive(Subject subject, Object otherArg)
{
System.out.println("this is Observer1");
}
}
class Observer2 implements Observer
{
@Override
public void receive(Subject subject, Object otherArg) {
System.out.println("this is Observer2");
}
}
3)程序分析部分
希望上面的程序大家能看懂,下面主要針對程序需要說明幾點注意事項朵夏。
(1)Java類庫中蔼啦,被監(jiān)聽者Subject實際上是一個類,而此處是一個接口仰猖。那么到底哪個比較好呢捏肢?
如果是接口,就必須維護一個ArrayList列表(接口中都是public成員饥侵,這樣就暴露了列表成員)鸵赫,來存儲申請注冊的監(jiān)聽者。而如果是類躏升,則沒有這么多麻煩辩棒,直接調用addObserver(),添加監(jiān)聽者即可膨疏。也就是說一睁,接口破壞了封裝性。
但是佃却,如果是類者吁,由于Java只支持單繼承,因此這樣做子類就不能繼承其他類饲帅,也不好用复凳。
(2)Observer接口中,receive()函數傳遞的參數可以根據需要自己設定灶泵,但是需要考慮兩個方面:
a染坯、參數不能太多,否則會增加通信量丘逸,嚴重影響程序的執(zhí)行效率单鹿。
b、參數不能太少深纲,因為這個函數是所有類接收信息的統(tǒng)一接口仲锄,一旦未來功能要擴展劲妙,就需要修改很多的東西——凡是實現receive()方法的類全都需要修改。
所以此處我把接口設計成接收兩個參數儒喊,一個是被監(jiān)聽者镣奋,一個是其他類。如果需要被監(jiān)聽者的成員變量怀愧,可以采用getter 和 setter方法侨颈。如果需要獲取其他信息,可以將這些信息全部打包在一個容器類里芯义,通過otherArg來傳遞哈垢。
(3)對于PostOffice類,有三個函數:setState()函數來改變當前對象的狀態(tài)扛拨,stateChanged()函數由用戶自定義耘分,來執(zhí)行當對象狀態(tài)改變時,需要作出的動作绑警。最后一個notifyObserver()才是真正通知監(jiān)聽者求泰,通知他們。為什么要這么麻煩计盒,直接將notifyObserver()放到setState()中不是更好渴频?
這樣做將:改變對象狀態(tài),改變對象狀態(tài)后應該做的操作北启,通知監(jiān)聽者枉氮,三個操作進行了分離。將操作的粒度變小暖庄,從而讓修改更加容易聊替。
同時,還要注意培廓,對于郵局這個模型惹悄,報紙來了通知一下就好。但是對于溫度檢測等模型肩钠,變化速度太快泣港,而響應速度又不太高(每秒變化一度,但是變化十度才做出反應)這樣的情況价匠,設置上面的三個函數就很有效当纱。
順便說下,上面的函數依然有文章可做踩窖∑侣龋可以在Subject接口中再增加一個setchanged()函數。下面只是說下增加到PostOffice中的代碼。增加了setChanged()箫柳,好處是可以根據當前狀態(tài)手形,來決定到底通不通知監(jiān)聽者。
private boolean changed=false;
public void stateChanged()//用戶自定義
{
this.setChanged(false);//注意這行悯恍,通知完后库糠,就表明沒有改變了
this.notifyObserver(null);
}
public void setState(boolean newspaper, boolean gift) //用戶自定義
{
this.newsPaper=newspaper;
this.gift=gift;
this.setChanged(true);//注意這行,發(fā)生了改變
this.stateChanged();
}
public void setChanged(boolean changed)
{
this.changed=changed;
}
至此涮毫,觀察模式結束瞬欧。碼了這么多字,好累罢防。