在許多設計中,常常涉及多個對象都對一個特殊對象中的數據變化感興趣,並且這多個對象都但願跟蹤那個特殊對象中的數據變化,在這樣的狀況下就可使用觀察者模式。java
例如,某些尋找工做的人對「求職中心」的職業需求信息的變化很是關心,很想追蹤「求職中心」中職業需求信息的變化。一位想知道「求職中心」職業需求信息變化的人須要成爲「求職中心」的求職者,即讓求職中心把本身登記到求職中心的「求職者」列表中,當一我的成爲求職中心的求職者後,求職中心就會及時通知他最新的職業需求信息。程序員
觀察者模式是關於多個對象想知道一個對象中數據變化狀況的一種成熟的模式。觀察者模式中有一個稱做「主題」的對象和若干個稱做「觀察者」的對象,「主題」和「觀察者」間是一種一對多的依賴關係,當「主題」的狀態發生變化時,全部「觀察者」都獲得通知。前面所述的「求職中心」至關於觀察者模式的一個具體「主題」;每一個「求職者」至關於觀察者模式中的一個具體「觀察者」。設計模式
觀察者模式的結構中包含四種角色:數組
(1)主題(Subject):主題是一個接口,該接口規定了具體主題須要實現的方法,好比,添加、刪除觀察者以及通知觀察者更新數據的方法。框架
(2)觀察者(Observer):觀察者是一個接口,該接口規定了具體觀察者用來更新數據的方法。dom
(3)具體主題(ConcreteSubject):具體主題是實現主題接口類的一個實例,該實例包含有能夠常常發生變化的數據。具體主題需使用一個集合,好比ArrayList,存放觀察者的引用,以便數據變化時通知具體觀察者。動畫
(4)具體觀察者(ConcreteObserver):具體觀察者是實現觀察者接口類的一個實例。具體觀察者包含有能夠存放具體主題引用的主題接口變量,以便具體觀察者讓具體主題將本身的引用添加到具體主題的集合中,使本身成爲它的觀察者,或讓這個具體主題將本身從具體主題的集合中刪除,使本身再也不是它的觀察者。ui
觀察者模式結構的類圖以下所示:this
(1)具體主題和具體觀察者是鬆耦合關係。因爲主題接口僅僅依賴於觀察者接口,所以具體主題只是知道它的觀察者是實現觀察者接口的某個類的實例,但不須要知道具體是哪一個類。一樣,因爲觀察者僅僅依賴於主題接口,所以具體觀察者只是知道它依賴的主題是實現主題接口的某個類的實例,但不須要知道具體是哪一個類。spa
(2)觀察者模式知足「開-閉原則」。主題接口僅僅依賴於觀察者接口,這樣,就可讓建立具體主題的類也僅僅是依賴於觀察者接口,所以,若是增長新的實現觀察者接口的類,沒必要修改建立具體主題的類的代碼。。一樣,建立具體觀察者的類僅僅依賴於主題接口,若是增長新的實現主題接口的類,也沒必要修改建立具體觀察者類的代碼。
(1)當一個對象的數據更新時須要通知其餘對象,但這個對象又不但願和被通知的那些對象造成緊耦合。
(2)當一個對象的數據更新時,這個對象須要讓其餘對象也各自更新本身的數據,但這個對象不知道具體有多少對象須要更新數據。
下面經過一個簡單的問題來描述觀察者模式中所涉及的各個角色,這個簡單的問題是:有一個大學畢業生和一個歸國留學者都但願能及時知道「求職中心」最炫的職業需求信息。
首先看一下本實例構建框架具體類和1.2模式的結構中類圖的對應關係,以下圖3所示:
圖3 具體編寫類及接口與類圖對應關係
(1)主題(Subject)
本問題中,主題接口Subject規定了具體主題須要實現的添加、刪除觀察者以及通知觀察者更新數據的方法。其代碼以下:
package com.liuzhen.two_observer; public interface Subject { public void addObserver(Observer o); public void deleteObserver(Observer o); public void notifyObservers(); }
(2)觀察者(Observer)
觀察者是一個接口,該接口規定了具體觀察者用來更新數據的方法。對於本問題,觀察者規定的方法是:hearTelephone()(至關於觀察者模式類圖中的update()方法),即要求具體觀察者都經過實現hearTelephone()方法(模擬接聽電話)來更新數據。其代碼以下:
package com.liuzhen.two_observer; public interface Observer { public void hearTelephone(String heardMess); }
(3)具體主題(ConcreteSubject)
具體主題維護着一個String字符串,用來表示「求職中心」的職業需求信息,當該String字符串發生變化時,具體主題遍歷存放觀察者引用集合。具體代碼以下:
package com.liuzhen.two_observer; import java.util.ArrayList; public class SeekJobCenter implements Subject{ String mess; boolean changed; ArrayList<Observer> personList; //存放觀察者引用的數組線性表 SeekJobCenter(){ personList = new ArrayList<Observer>(); mess = ""; changed = false; } public void addObserver(Observer o){ if(!(personList.contains(o))){ personList.add(o); //把觀察者的引用添加到數組線性表 } } public void deleteObserver(Observer o){ if(personList.contains(o)){ personList.remove(o); //把觀察者的引用移除數組線性表 } } public void notifyObservers(){ if(changed){ //通知全部的觀察者 for(int i = 0;i < personList.size();i++){ Observer observer = personList.get(i); observer.hearTelephone(mess); //讓全部的觀察者接聽電話 } } } public void getNewMess(String str){ //判斷信息是不是新發布的 if(str.equals(mess)){ changed = false; } else{ mess = str; changed = true; } } }
(4)具體觀察者(ConcreteObserver)
本問題中,實現觀察者接口Observer的類有兩個:一個UniversityStudent類,另外一個是HaiGui。UniversityStudent類的實例調用hearTelephone(String heardMess)方法時,會將參數引用的字符串保存到一個文件中。HaiGui類的實例調用hearTelephone(String heardMess)方法時,若是參數引用的字符串中包含有「程序員」或軟件,就將信息保存到一個文件中。
UniversityStudent類代碼以下:
package com.liuzhen.two_observer; import java.io.*; public class UniversityStudent implements Observer { Subject subject; File myFile; UniversityStudent(Subject subject,String fileName){ this.subject = subject; subject.addObserver(this); //使當前實例成爲subject所使用的具體主題的觀察者 myFile = new File(fileName); } public void hearTelephone(String heardMess) { try{ RandomAccessFile out1 = new RandomAccessFile(myFile,"rw"); out1.seek(out1.length()); byte[] b = heardMess.getBytes(); out1.write(b); //更新文件中的內容 System.out.print("我是一個大學生,"); System.out.println("我向文件"+myFile.getName()+"寫入以下內容:"); System.out.println(heardMess); } catch(IOException exp){ System.out.println(exp.toString()); } } }
HaiGui類代碼以下:
package com.liuzhen.two_observer; import java.io.*; public class HaiGui implements Observer { Subject subject; File myFile; HaiGui(Subject subject , String fileName){ this.subject = subject; subject.addObserver(this); //使當前實例成爲subject所引用的具體主題的觀察者 myFile = new File(fileName); } public void hearTelephone(String heardMess) { try{ boolean boo = heardMess.contains("java程序員")||heardMess.contains("軟件"); if(boo){ RandomAccessFile out = new RandomAccessFile(myFile,"rw"); out.seek(out.length()); byte[] b = heardMess.getBytes(); out.write(b); System.out.print("我是一個海歸"); System.out.println("我向文件"+myFile.getName()+"寫入以下內容:"); System.out.println(heardMess); } else{ System.out.println("我是海歸,此次的信息中沒有我須要的信息"); } } catch(IOException exp){ System.out.println(exp.toString()); } } }
(5)具體調用實現
經過TwoApllication類來具體實現上述相關類和接口,來實現觀察者模式的運用,其代碼以下:
package com.liuzhen.two_observer; public class TwoApplication { public static void main(String[] args) { SeekJobCenter center = new SeekJobCenter(); //具體主題center UniversityStudent zhanglin = new UniversityStudent(center,"A.txt"); //具體觀察者zhanglin HaiGui wanghao = new HaiGui(center,"B.txt"); //具體觀察者wanghao center.getNewMess("騰輝公司須要10個Java程序員。"); //具體主題給出新信息 center.notifyObservers(); //具體主題通知信息 center.getNewMess("海景公司須要8個動畫設計師。"); center.notifyObservers(); center.getNewMess("仁海公司須要9個電工。"); center.notifyObservers(); center.getNewMess("仁海公司須要9個電工。"); //信息不是新的 center.notifyObservers(); //觀察者不會執行更新操做 } }
運行結果:
我是一個大學生,我向文件A.txt寫入以下內容:
騰輝公司須要10個Java程序員。
我是海歸,此次的信息中沒有我須要的信息
我是一個大學生,我向文件A.txt寫入以下內容:
海景公司須要8個動畫設計師。
我是海歸,此次的信息中沒有我須要的信息
我是一個大學生,我向文件A.txt寫入以下內容:
仁海公司須要9個電工。
我是海歸,此次的信息中沒有我須要的信息
參考資料:
1.Java設計模式/耿祥義,張躍平著.——北京:清華大學出版社,2009.5