一、觀察者模式定義
觀察者模式定義了對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都將得到通知,並自動更新。
二、觀察者模式的結構和說明
- Subject 被觀察者(目標對象)
它管理着觀察它的觀察者,提供添加和刪除觀察者的接口,並在自身有變化時,通知所有添加的觀察者。 - Observer 觀察者
觀察者可以接收被觀察者發送過來的消息。 - ConcreteSubject 具體的被觀察者
定義被觀察者具體的業務邏輯,同時定義對哪些事件進行通知。 - ConcreteObserver 具體的觀察者
定義具體的接收到消息後的處理邏輯。
三、觀察者模式示例
假設這麼一個場景:博主開設了一個設計模式專欄,當博主專欄裏發佈新的博客時,訂閱該專欄的用戶可以收到通知。
Subject 被觀察者(目標對象)
/**
* 被觀察者(目標對象)
* 它管理着觀察它的觀察者,提供添加和刪除觀察者的接口,並在自身有變化時,通知所有添加的觀察者。
*/
public class Subject {
/**
* 用來保存添加的觀察者對象
*/
private List<Observer> observers = new ArrayList<>();
/**
* 添加觀察者對象
* @param observer 觀察者對象
*/
public void addObserver(Observer observer){
observers.add(observer);
}
/**
* 刪除觀察者對象
* @param observer 觀察者對象
*/
public void deleteObserver(Observer observer){
observers.remove(observer);
}
/**
* 通知所有觀察者
* @param content
*/
protected void notifyObservers(String content){
for(Observer observer : observers){
observer.update(this, content);
}
}
}
Observer 觀察者
/**
* 觀察者
* 觀察者可以接收被觀察者發送過來的消息。
*/
public interface Observer {
/**
* 接收通知的方法
* @param subject 具體的被觀察對象,可以通過拉的方式獲取內容
* @param content (直接推送過來的)新內容
*/
void update(Subject subject, String content);
}
BlogSubject 博客專欄(具體的被觀察者)
/**
* 博客專欄
*/
public class BlogSubject extends Subject{
/**
* 專欄名稱
*/
private String name;
/**
* 存放專欄裏的博客
*/
private List<String> articles;
public BlogSubject(String name) {
this.name = name;
}
public String getName() {
return name;
}
public List<String> getArticles() {
return articles;
}
/**
* 發佈新博客
* @param article
*/
public void addArticle(String article) {
if(this.articles == null){
this.articles = new ArrayList<>();
}
this.articles.add(0,article);
// 有新博客了,通知關注者
this.notifyObservers(article);
}
}
CsdnUserObserver CSDN用戶(具體的觀察者)
/**
* CSDN用戶
*/
public class CsdnUserObserver implements Observer{
/**
* 用戶暱稱
*/
private String userName;
public CsdnUserObserver(String userName) {
this.userName = userName;
}
/**
* 接收消息
* @param subject 具體的被觀察對象,可以通過拉的方式獲取內容
* @param content (直接推送過來的)新內容
*/
@Override
public void update(Subject subject, String content) {
// 輸出推送過來的信息
System.out.println(this.userName+" 閱讀了:"+content);
// 去拉取最新信息
System.out.println(this.userName+" 閱讀了:"+
((BlogSubject)subject).getName()+" -> "+((BlogSubject) subject).getArticles().get(0));
}
}
以上,基本觀察者模式的結構就已經設計好了,下邊我們來模擬一下使用。
public class Client {
public static void main(String[] args) {
//創建一個專欄,作爲具體的被觀察者
Subject subject = new BlogSubject("設計模式專欄");
// 創建三個觀察者
CsdnUserObserver observer1 = new CsdnUserObserver("張無忌");
CsdnUserObserver observer2 = new CsdnUserObserver("令狐沖");
CsdnUserObserver observer3 = new CsdnUserObserver("蕭峯");
// 添加觀察者
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.addObserver(observer3);
//博主發佈新博客了
((BlogSubject) subject).addArticle("Java設計模式及應用場景之《觀察者模式》");
}
}
執行main方法後,得到下邊的輸出:
張無忌 閱讀了:Java設計模式及應用場景之《觀察者模式》
張無忌 閱讀了:設計模式專欄 -> Java設計模式及應用場景之《觀察者模式》
令狐沖 閱讀了:Java設計模式及應用場景之《觀察者模式》
令狐沖 閱讀了:設計模式專欄 -> Java設計模式及應用場景之《觀察者模式》
蕭峯 閱讀了:Java設計模式及應用場景之《觀察者模式》
蕭峯 閱讀了:設計模式專欄 -> Java設計模式及應用場景之《觀察者模式》
四、用Java自帶的功能實現觀察者模式
觀察者模式是Java非常重要的一個設計模式。對於觀察者模式,JDK已經爲我們提供了對應的接口和類。分別是:java.util.Observable
(被觀察者);java.util.Observer
(觀察者)。
下面我們用Java提供的功能來實現觀察者模式示例。
BlogSubject 博客專欄(具體的被觀察者)
/**
* 博客專欄
*/
public class BlogSubject extends Observable {
/**
* 專欄名稱
*/
private String name;
/**
* 存放專欄裏的博客
*/
private List<String> articles;
public BlogSubject(String name) {
this.name = name;
}
public String getName() {
return name;
}
public List<String> getArticles() {
return articles;
}
/**
* 發佈新博客
* @param article
*/
public void addArticle(String article) {
if(this.articles == null){
this.articles = new ArrayList<>();
}
this.articles.add(0,article);
//注意在用Java中的Observer模式的時候,這句話不可少
this.setChanged();
// 有新博客了,通知關注者
this.notifyObservers(article);
// 如果單純使用拉的方式,可以用下邊這個方法
// this.notifyObservers();
}
}
CsdnUserObserver CSDN用戶(具體的觀察者)
/**
* CSDN用戶
*/
public class CsdnUserObserver implements Observer{
/**
* 用戶暱稱
*/
private String userName;
public CsdnUserObserver(String userName) {
this.userName = userName;
}
/**
* 接收消息
* @param o 具體的被觀察對象,可以通過拉的方式獲取內容
* @param arg (直接推送過來的)新內容
*/
@Override
public void update(Observable o, Object arg) {
// 輸出推送過來的信息
System.out.println(this.userName+" 閱讀了:"+arg.toString());
// 去拉取最新信息
System.out.println(this.userName+" 閱讀了:"+
((BlogSubject)o).getName()+" -> "+((BlogSubject) o).getArticles().get(0));
}
}
以上,觀察者模式的結構就已經重新設計好了,下邊我們再來模擬一下使用。
public class Client {
public static void main(String[] args) {
//創建一個專欄,作爲具體的被觀察者
Observable observable = new BlogSubject("設計模式專欄");
// 創建三個觀察者
CsdnUserObserver observer1 = new CsdnUserObserver("張無忌");
CsdnUserObserver observer2 = new CsdnUserObserver("令狐沖");
CsdnUserObserver observer3 = new CsdnUserObserver("蕭峯");
// 添加觀察者
observable.addObserver(observer1);
observable.addObserver(observer2);
observable.addObserver(observer3);
//博主發佈新博客了
((BlogSubject) observable).addArticle("Java設計模式及應用場景之《觀察者模式》");
}
}
執行main方法後,得到同樣的輸出:
蕭峯 閱讀了:Java設計模式及應用場景之《觀察者模式》
蕭峯 閱讀了:設計模式專欄 -> Java設計模式及應用場景之《觀察者模式》
令狐沖 閱讀了:Java設計模式及應用場景之《觀察者模式》
令狐沖 閱讀了:設計模式專欄 -> Java設計模式及應用場景之《觀察者模式》
張無忌 閱讀了:Java設計模式及應用場景之《觀察者模式》
張無忌 閱讀了:設計模式專欄 -> Java設計模式及應用場景之《觀察者模式》
用Java自帶的功能實現觀察者模式,相較於完全自己實現,有以下一些改變:
- 不需要自己定義觀察者和被觀察者接口。
- 觸發通知時,需要先調用
setChanged()
方法,這個是Java爲了實現更精確的觸發控制而提供的功能。 - Java提供的功能做了線程安全的處理(具體實現可以看源碼)。
五、觀察者模式的優缺點
優點:
- Subject(被觀察者)和Observer(觀察者)之間是松耦合的,分別可以各自獨立改變。
- 觀察者模式實現了動態聯動,支持廣播通信(被觀察者會向所有的登記過的觀察者發出通知)。
缺點:
- 如果一個Subject被大量Observer訂閱的話,在廣播通知的時候可能會有效率問題。並且,如果是順序執行,中間某個環節卡殼,會影響到後續流程的執行(針對這個缺點,可以考慮採用異步的方式處理)。
六、觀察者模式的應用場景
-
對一個對象狀態的更新,需要其他對象同步更新,而且其他對象的數量動態可變。
如,用戶支付成功一個訂單時,會觸發這些操作:- 記錄文本日誌
- 發送短信消息
- 贈送活動優惠券
並且以後還會擴展一些其它的操作,這時比較適合用觀察者模式。
-
對象僅需要將自己的更新通知給其他對象而不需要知道其他對象的細節。
-
Swing中的事件處理(Swing組件是被觀察者,每個實現監聽器的類就是觀察者)。
-
Spring ApplicationContext 事件機制中的觀察者模式。