設計模式--觀察者模式(Observer)

觀察者模式(Observer)

在現(xiàn)實世界中,許多對象并不是獨立存在的嘉汰,其中一個對象的行為發(fā)生改變可能會導致一個或者多個其他對象的行為也發(fā)生改變。例如状勤,某種商品的物價上漲時會導致部分商家高興鞋怀,而消費者傷心双泪;還有,當我們開車到交叉路口時密似,遇到紅燈會停焙矛,遇到綠燈會行。這樣的例子還有很多残腌,例如村斟,股票價格與股民、微信公眾號與微信用戶抛猫、氣象局的天氣預報與聽眾蟆盹、小偷與警察等。
在軟件世界也是這樣闺金,例如逾滥,Excel 中的數(shù)據(jù)與折線圖、餅狀圖败匹、柱狀圖之間的關系寨昙;MVC 模式中的模型與視圖的關系;事件模型中的事件源與事件處理者掀亩。所有這些舔哪,如果用觀察者模式來實現(xiàn)就非常方便。

模式的定義與特點

  • 觀察者(Observer)模式的定義:
    指多個對象間存在一對多的依賴關系槽棍,當一個對象的狀態(tài)發(fā)生改變時捉蚤,所有依賴于它的對象都得到通知并被自動更新。這種模式有時又稱作發(fā)布-訂閱模式刹泄、模型-視圖模式外里,它是對象行為型模式。
  • 觀察者(Observer)模式的優(yōu)點:
    1.降低了目標與觀察者之間的耦合關系特石,兩者之間是抽象耦合關系盅蝗。
    2.目標與觀察者之間建立了一套觸發(fā)機制。
  • 觀察者(Observer)模式的缺點:
    1.目標與觀察者之間的依賴關系并沒有完全解除姆蘸,而且有可能出現(xiàn)循環(huán)引用墩莫。
    2.當觀察者對象很多時,通知的發(fā)布會花費很多時間逞敷,影響程序的效率狂秦。

模式的結構與實現(xiàn)

實現(xiàn)觀察者模式時要注意具體目標對象和具體觀察者對象之間不能直接調(diào)用,否則將使兩者之間緊密耦合起來推捐,這違反了面向?qū)ο蟮脑O計原則裂问。

1. 模式的結構

觀察者模式的主要角色如下。

  1. 抽象主題(Subject)角色: 也叫抽象目標類,它提供了一個用于保存觀察者對象的聚集類和增加堪簿、刪除觀察者對象的方法痊乾,以及通知所有觀察者的抽象方法。
  2. 具體主題(Concrete Subject)角色: 也叫具體目標類椭更,它實現(xiàn)抽象目標中的通知方法哪审,當具體主題的內(nèi)部狀態(tài)發(fā)生改變時,通知所有注冊過的觀察者對象虑瀑。
  3. 抽象觀察者(Observer)角色: 它是一個抽象類或接口湿滓,它包含了一個更新自己的抽象方法,當接到具體主題的更改通知時被調(diào)用舌狗。
  4. 具體觀察者(Concrete Observer)角色:實現(xiàn)抽象觀察者中定義的抽象方法叽奥,以便在得到目標的更改通知時更新自身的狀態(tài)。
    觀察者模式的結構圖如圖 1 所示把夸。
    圖1 觀察者模式的結構圖
2. 模式的實現(xiàn)

觀察者模式的實現(xiàn)代碼如下:

package observer;
import java.util.*;
public class ObserverPattern
{
    public static void main(String[] args)
    {
        Subject subject=new ConcreteSubject();
        Observer obs1=new ConcreteObserver1();
        Observer obs2=new ConcreteObserver2();
        subject.add(obs1);
        subject.add(obs2);
        subject.notifyObserver();
    }
}
//抽象目標
abstract class Subject
{
    protected List<Observer> observers=new ArrayList<Observer>();   
    //增加觀察者方法
    public void add(Observer observer)
    {
        observers.add(observer);
    }    
    //刪除觀察者方法
    public void remove(Observer observer)
    {
        observers.remove(observer);
    }   
    public abstract void notifyObserver(); //通知觀察者方法
}
//具體目標
class ConcreteSubject extends Subject
{
    public void notifyObserver()
    {
        System.out.println("具體目標發(fā)生改變...");
        System.out.println("--------------");       
       
        for(Object obs:observers)
        {
            ((Observer)obs).response();
        }
       
    }          
}
//抽象觀察者
interface Observer
{
    void response(); //反應
}
//具體觀察者1
class ConcreteObserver1 implements Observer
{
    public void response()
    {
        System.out.println("具體觀察者1作出反應而线!");
    }
}
//具體觀察者1
class ConcreteObserver2 implements Observer
{
    public void response()
    {
        System.out.println("具體觀察者2作出反應!");
    }
}

程序運行結果如下:

具體目標發(fā)生改變...
--------------
具體觀察者1作出反應恋日!
具體觀察者2作出反應膀篮!

模式的實例

利用觀察者模式設計一個程序,分析“人民幣匯率”的升值或貶值對進口公司的進口產(chǎn)品成本或出口公司的出口產(chǎn)品收入以及公司的利潤率的影響岂膳。

分析:當“人民幣匯率”升值時誓竿,進口公司的進口產(chǎn)品成本降低且利潤率提升,出口公司的出口產(chǎn)品收入降低且利潤率降低谈截;當“人民幣匯率”貶值時筷屡,進口公司的進口產(chǎn)品成本提升且利潤率降低,出口公司的出口產(chǎn)品收入提升且利潤率提升簸喂。

這里的匯率(Rate)類是抽象目標類毙死,它包含了保存觀察者(Company)的 List 和增加/刪除觀察者的方法,以及有關匯率改變的抽象方法 change(int number)喻鳄;而人民幣匯率(RMBrate)類是具體目標扼倘, 它實現(xiàn)了父類的 change(int number) 方法,即當人民幣匯率發(fā)生改變時通過相關公司除呵;公司(Company)類是抽象觀察者再菊,它定義了一個有關匯率反應的抽象方法 response(int number);進口公司(ImportCompany)類和出口公司(ExportCompany)類是具體觀察者類颜曾,它們實現(xiàn)了父類的 response(int number) 方法纠拔,即當它們接收到匯率發(fā)生改變的通知時作為相應的反應。圖 2 所示是其結構圖泛豪。


圖2 人民幣匯率分析程序的結構圖

程序代碼如下:

package observer;
import java.util.*;
public class RMBrateTest
{
    public static void main(String[] args)
    {
        Rate rate=new RMBrate();         
        Company watcher1=new ImportCompany(); 
        Company watcher2=new ExportCompany();           
        rate.add(watcher1); 
        rate.add(watcher2);           
        rate.change(10);
        rate.change(-9);
    }
}
//抽象目標:匯率
abstract class Rate
{
    protected List<Company> companys=new ArrayList<Company>();   
    //增加觀察者方法
    public void add(Company company)
    {
        companys.add(company);
    }    
    //刪除觀察者方法
    public void remove(Company company)
    {
        companys.remove(company);
    }   
    public abstract void change(int number);
}
//具體目標:人民幣匯率
class RMBrate extends Rate 
{
    public void change(int number)
    {       
        for(Company obs:companys)
        {
            ((Company)obs).response(number);
        }       
    }

}
//抽象觀察者:公司
interface Company
{
    void response(int number);
}
//具體觀察者1:進口公司 
class ImportCompany implements Company 
{
    public void response(int number) 
    {
        if(number>0)
        {
            System.out.println("人民幣匯率升值"+number+"個基點稠诲,降低了進口產(chǎn)品成本侦鹏,提升了進口公司利潤率。"); 
        }
        else if(number<0)
        {
              System.out.println("人民幣匯率貶值"+(-number)+"個基點臀叙,提升了進口產(chǎn)品成本种柑,降低了進口公司利潤率。"); 
        }
    } 
} 
//具體觀察者2:出口公司
class ExportCompany implements Company 
{
    public void response(int number) 
    {
        if(number>0)
        {
            System.out.println("人民幣匯率升值"+number+"個基點匹耕,降低了出口產(chǎn)品收入,降低了出口公司的銷售利潤率荠雕。"); 
        }
        else if(number<0)
        {
              System.out.println("人民幣匯率貶值"+(-number)+"個基點稳其,提升了出口產(chǎn)品收入,提升了出口公司的銷售利潤率炸卑。"); 
        }
    } 
}

程序運行結果如下:

人民幣匯率升值10個基點既鞠,降低了進口產(chǎn)品成本,提升了進口公司利潤率盖文。
人民幣匯率升值10個基點嘱蛋,降低了出口產(chǎn)品收入,降低了出口公司的銷售利潤率五续。
人民幣匯率貶值9個基點洒敏,提升了進口產(chǎn)品成本,降低了進口公司利潤率疙驾。
人民幣匯率貶值9個基點凶伙,提升了出口產(chǎn)品收入,提升了出口公司的銷售利潤率它碎。

模式的應用場景

通過前面的分析與應用實例可知觀察者模式適合以下幾種情形函荣。

  • 對象間存在一對多關系,一個對象的狀態(tài)發(fā)生改變會影響其他對象扳肛。
  • 當一個抽象模型有兩個方面傻挂,其中一個方面依賴于另一方面時,可將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和復用挖息。

模式的擴展

在 Java中金拒,通過 java.util.Observable 類和 java.util.Observer 接口定義了觀察者模式,只要實現(xiàn)它們的子類就可以編寫觀察者模式實例旋讹。

1. Observable類

Observable 類是抽象目標類殖蚕,它有一個 Vector 向量,用于保存所有要通知的觀察者對象沉迹,下面來介紹它最重要的 3 個方法睦疫。

  1. void addObserver(Observer o) 方法: 用于將新的觀察者對象添加到向量中。
  2. void notifyObservers(Object arg) 方法: 調(diào)用向量中的所有觀察者對象的 update鞭呕。方法蛤育,通知它們數(shù)據(jù)發(fā)生改變。通常越晚加入向量的觀察者越先得到通知。
  3. void setChange() 方法: 用來設置一個 boolean 類型的內(nèi)部標志位瓦糕,注明目標對象發(fā)生了變化底洗。當它為真時,notifyObservers() 才會通知觀察者咕娄。
2. Observer 接口

Observer 接口是抽象觀察者亥揖,它監(jiān)視目標對象的變化,當目標對象發(fā)生變化時圣勒,觀察者得到通知费变,并調(diào)用 void update(Observable o,Object arg) 方法,進行相應的工作圣贸。

利用 Observable 類和 Observer 接口實現(xiàn)原油期貨的觀察者模式實例

分析:當原油價格上漲時挚歧,空方傷心,多方局興吁峻;當油價下跌時滑负,空方局興,多方傷心用含。本實例中的抽象目標(Observable)類在 Java 中已經(jīng)定義矮慕,可以直接定義其子類,即原油期貨(OilFutures)類啄骇,它是具體目標類凡傅,該類中定義一個 SetPriCe(float price) 方法,當原油數(shù)據(jù)發(fā)生變化時調(diào)用其父類的 notifyObservers(Object arg) 方法來通知所有觀察者肠缔;另外夏跷,本實例中的抽象觀察者接口(Observer)在 Java 中已經(jīng)定義,只要定義其子類明未,即具體觀察者類(包括多方類 Bull 和空方類 Bear)槽华,并實現(xiàn) update(Observable o,Object arg) 方法即可。圖 3 所示是其結構圖趟妥。


圖3 原油期貨的觀察者模式實例的結構圖

程序代碼如下:

package observer;
import java.util.Observer;
import java.util.Observable;
public class CrudeOilFutures
{
    public static void main(String[] args)
    {
        OilFutures oil=new OilFutures();
        Observer bull=new Bull(); //多方
        Observer bear=new Bear(); //空方
        oil.addObserver(bull);
        oil.addObserver(bear);
        oil.setPrice(10);
        oil.setPrice(-8);
    }
}
//具體目標類:原油期貨
class OilFutures extends Observable
{
    private float price;   
    public float getPrice()
    {
        return this.price; 
    }
    public void setPrice(float price)
    {
        super.setChanged() ;  //設置內(nèi)部標志位猫态,注明數(shù)據(jù)發(fā)生變化 
        super.notifyObservers(price);    //通知觀察者價格改變了 
        this.price=price ; 
    }
}
//具體觀察者類:多方
class Bull implements Observer
{   
    public void update(Observable o,Object arg)
    {
        Float price=((Float)arg).floatValue();
        if(price>0)
        {
            System.out.println("油價上漲"+price+"元,多方高興了披摄!");
        }
        else
        {
            System.out.println("油價下跌"+(-price)+"元亲雪,多方傷心了!");
        }
    }
}
//具體觀察者類:空方
class Bear implements Observer
{   
    public void update(Observable o,Object arg)
    {
        Float price=((Float)arg).floatValue();
        if(price>0)
        {
            System.out.println("油價上漲"+price+"元疚膊,空方傷心了义辕!");
        }
        else
        {
            System.out.println("油價下跌"+(-price)+"元,空方高興了寓盗!");
        }
    }
}

程序運行結果如下:

油價上漲10.0元灌砖,空方傷心了璧函!
油價上漲10.0元,多方高興了基显!
油價下跌8.0元蘸吓,空方高興了!
油價下跌8.0元撩幽,多方傷心了库继!
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市窜醉,隨后出現(xiàn)的幾起案子制跟,更是在濱河造成了極大的恐慌,老刑警劉巖酱虎,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異擂涛,居然都是意外死亡读串,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門撒妈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恢暖,“玉大人,你說我怎么就攤上這事狰右〗芪妫” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵棋蚌,是天一觀的道長嫁佳。 經(jīng)常有香客問我,道長谷暮,這世上最難降的妖魔是什么蒿往? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮湿弦,結果婚禮上瓤漏,老公的妹妹穿的比我還像新娘。我一直安慰自己颊埃,他們只是感情好蔬充,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著班利,像睡著了一般饥漫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上罗标,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天趾浅,我揣著相機與錄音愕提,去河邊找鬼。 笑死皿哨,一個胖子當著我的面吹牛浅侨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播证膨,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼如输,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了央勒?” 一聲冷哼從身側(cè)響起不见,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎崔步,沒想到半個月后稳吮,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡井濒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年灶似,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瑞你。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡酪惭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出者甲,到底是詐尸還是另有隱情春感,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布虏缸,位于F島的核電站鲫懒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏刽辙。R本人自食惡果不足惜刀疙,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扫倡。 院中可真熱鬧谦秧,春花似錦、人聲如沸撵溃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缘挑。三九已至集歇,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間语淘,已是汗流浹背诲宇。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工际歼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人姑蓝。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓鹅心,卻偏偏與公主長得像,于是被迫代替她去往敵國和親纺荧。 傳聞我的和親對象是個殘疾皇子旭愧,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容