在軟件開發中,觀察者模式是使用頻率最高的設計模式之一,若是你作過web開發,對它應該更不會陌生,由於典型的MVC架構就是對觀察者模式的一種延伸。在軟件開發中常常會碰到這種困境:系統由若干個相互協做的類構成,類之間常有一對多的依賴關係,當被依賴對象的狀態變化時,其餘全部依賴對象都要發生改變。以MVC爲例,模型(Model)對象封裝了數據,視圖(View)對象對數據進行渲染和進行圖形表示。當模型中的數據改變時,視圖應該立刻獲得反饋從而改變其顯示的內容。咱們須要維護這種具備依賴關係的對象之間的一致性,又不但願爲了維護這種一致性致使類之間緊密耦合。而觀察者模式正式對這一困境的回答。觀察者模式的的最大好處是能夠實現具備關聯關係的對象之間的解耦,使得雙方能夠獨立的進行擴展和變化,使得系統具備更好的彈性。web
觀察者模式定義了對象之間的一對多依賴關係,每當對象改變狀態,全部依賴於它的對象都會獲得通知並被自動更新。設計模式
觀察者模式的結構相對簡單,能夠用<Head First設計模式>
中的一張圖來描述安全
觀察者模式的主要角色:架構
Subject(主題)/Observable(被觀察者)
一般以抽象類或者接口的形式存在,定義了被觀察者即主題必須實現的職責:1.必須能動態的註冊和移除觀察者 2.在主題狀態改變時能通知觀察者進行更新。異步
Observer(觀察者)
定義了觀察者的主要職責:在主題狀態改變時須要進行更新,具體的更新邏輯由具體觀察者自行實現。ide
ConcreteSubject(具體的主題)/ConcreteObservable(具體的被觀察者)
根據業務實際實現抽象主題中定義的接口,並對特定的觀察者進行通知。post
ConcreteObserver(具體的觀察者)
根據業務實際實現本身的更新邏輯,在主題狀態改變時進行更新。性能
觀察者模式又被稱爲發佈訂閱模式,以客戶訂閱報紙爲例,客戶至關於觀察者,而報社則是被觀察者。客戶能夠向報社訂閱報紙,也能夠取消訂閱。當報社有新報紙出版時,就會將報紙發送給訂閱的客戶。測試
/** * @author: takumiCX * @create: 2018-10-29 **/ public abstract class Subject { //觀察者集合 private CopyOnWriteArrayList<Observer> observers=new CopyOnWriteArrayList<>(); //註冊觀察者 protected void registerObserver(Observer observer){ observers.add(observer); } //移除觀察者 protected boolean removeObserver(Observer observer){ return observers.remove(observer); } /** * 通知觀察者 * @param msg 發送給觀察者的消息 */ protected void notifyObservers(String msg){ for(Observer observer:observers){ observer.update(msg); } } /** * 通知觀察者 */ protected void notifyObservers(){ for(Observer observer:observers){ observer.update(); } } }
該抽象類內部維護了一個線程安全的CopyOnWriteArrayList來存儲觀察者集合,並無使用Vector或者SynchronizedList等常見同步容器,在須要頻繁增刪觀察者的狀況下能夠必定程度提高性能。this
/** * @author: takumiCX * @create: 2018-10-29 **/ public class NewsPaperSubject extends Subject { //報紙的期號 private String date; public String getDate() { return date; } public void setDate(String date) { this.date = date; } /** * 通知訂閱的客戶接收新一期的報紙 */ public void postNewPublication(){ notifyObservers(date); } }
具體的主題實現類繼承了主題抽象類,並添加了一個狀態變量,表示報紙的期號,在通知訂閱的客戶時須要將該信息也一塊兒傳過去。postNewPublication()
方法當有新一期的報紙發行時,會經過調用該方法對訂閱的客戶進行通知。
/** * @author: takumiCX * @create: 2018-10-29 **/ public interface Observer { void update(String msg); void update(); }
/** * @author: takumiCX * @create: 2018-10-29 **/ public class CustomerObserver implements Observer { //客戶姓名 private String name; public CustomerObserver(String name) { this.name = name; } @Override public void update(String msg) { System.out.println(name+" 您好!"+msg+" 期的報紙已發送,請注意接收!"); } @Override public void update() { } }
uml類結構
/** * @author: takumiCX * @create: 2018-10-29 **/ public class Test { public static void main(String[] args) { //報社(主題) NewsPaperSubject newsPaperSubject = new NewsPaperSubject(); //客戶1(觀察者) CustomerObserver observer1 = new CustomerObserver("趙雲"); //客戶2(觀察者) CustomerObserver observer2 = new CustomerObserver("馬超"); CustomerObserver observer3 = new CustomerObserver("張飛"); //向主題註冊觀察者 newsPaperSubject.registerObserver(observer1); newsPaperSubject.registerObserver(observer2); newsPaperSubject.registerObserver(observer3); //報紙的期號 String date="2018-10-29"; newsPaperSubject.setDate(date); //通知全部訂閱的客戶接收報紙 newsPaperSubject.postNewPublication(); } }
JDK內置了對觀察者模式的支持,只要繼承或者實現相應的抽象類或接口
/** * @author: takumiCX * @create: 2018-10-29 **/ public class JDKNewsPaperObservable extends Observable { //報紙的期號 private String date; public String getDate() { return date; } public void setDate(String date) { this.date = date; } public void postNewPublication(){ //將狀態改變的標誌位置位true setChanged(); //通知全部觀察者 notifyObservers(date); } }
JKD中對主題經過抽象類Observable進行了抽象,實現自定義主題只要繼承該抽象類便可。注意該抽象類內部有一個標註主題狀態是否改變的標誌位,默認爲false
private boolean changed = false;
在通知觀察者前必須先經過調用setChanged()方法將該標誌位置爲true。在通知觀察者進行更新的方法被調用後,該標誌位會被從新置爲false。
/** * @author: takumiCX * @create: 2018-10-29 **/ public class JDKCustomerObserver implements Observer { //客戶姓名 private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public JDKCustomerObserver(String name) { this.name = name; } @Override public void update(Observable o, Object arg) { System.out.println(name+" 您好!"+arg+" 期的報紙已發送,請注意接收!"); } }
uml類結構
/** * @author: takumiCX * @create: 2018-10-29 **/ public class Test2 { public static void main(String[] args) { //報社(主題) JDKNewsPaperObservable jdkNewsPaperObservable = new JDKNewsPaperObservable(); //客戶1(觀察者) JDKCustomerObserver observer1 = new JDKCustomerObserver("趙雲"); //客戶2(觀察者) JDKCustomerObserver observer2 = new JDKCustomerObserver("馬超"); JDKCustomerObserver observer3 = new JDKCustomerObserver("張飛"); //向主題註冊觀察者 jdkNewsPaperObservable.addObserver(observer1); jdkNewsPaperObservable.addObserver(observer2); jdkNewsPaperObservable.addObserver(observer3); //報紙的期號 String date="2018-10-29"; jdkNewsPaperObservable.setDate(date); //通知全部訂閱的客戶接收報紙 jdkNewsPaperObservable.postNewPublication(); } }
1.多個觀察者默認是被順序調用而執行的,當一個觀察者的業務邏輯執行卡頓,或者執行時間過長,會致使後續觀察者的業務邏輯執行被延遲,也會影響總體的執行效率。
解決辦法:採用異步的方式進行處理,好比將觀察者的業務邏輯放到線程池中去執行。
2.當多個對象既是觀察者又是被觀察者將致使系統難以調試和維護。
解決辦法:不容許觀察者模式中存在既是觀察者又是被觀察者的對象。
當存在相互關聯的對象,即某些對象狀態的改變會致使其餘對象產生相應的變化。使用觀察者模式能夠方便的維護關聯對象間行爲的一致性,同時使其保持鬆耦合狀態,這樣雙方就能夠相對獨立的進行擴展和變化,使得系統更具彈性。