## 引入
在閻宏博士的《JAVA與模式》一書中開頭是這樣描述觀察者(Observer)模式的:
> 觀察者模式是對象的行為模式,又叫發布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。
> 觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。
## 定義
何謂觀察者模式?觀察者模式定義了對象之間的一對多依賴關系,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知并且自動更新。
在這里,發生改變的對象稱之為觀察目標,而被通知的對象稱之為觀察者。一個觀察目標可以對應多個觀察者,而且這些觀察者之間沒有相互聯系,所以么可以根據需要增加和刪除觀察者,使得系統更易于擴展。
## 結構
一個軟件系統里面包含了各種對象,就像一片欣欣向榮的森林充滿了各種生物一樣。在一片森林中,各種生物彼此依賴和約束,形成一個個生物鏈。一種生物的狀態變化會造成其他一些生物的相應行動,每一個生物都處于別的生物的互動之中。
同樣,一個軟件系統常常要求在某一個對象的狀態發生變化的時候,某些其他的對象做出相應的改變。做到這一點的設計方案有很多,但是為了使系統能夠易于復用,應該選擇低耦合度的設計方案。減少對象之間的耦合有利于系統的復用,但是同時設計師需要使這些低耦合度的對象之間能夠維持行動的協調一致,保證高度的協作。觀察者模式是滿足這一要求的各種設計方案中最重要的一種。
下面以一個簡單的示意性實現為例,討論觀察者模式的結構。

觀察者模式所涉及的角色有:
* 抽象主題(Subject)角色:抽象主題角色把所有對觀察者對象的引用保存在一個聚集(比如ArrayList對象)里,每個主題都可以有任何數量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象,抽象主題角色又叫做抽象被觀察者(Observable)角色。
* 具體主題(ConcreteSubject)角色:將有關狀態存入具體觀察者對象;在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色又叫做具體被觀察者(Concrete Observable)角色。
* 抽象觀察者(Observer)角色:為所有的具體觀察者定義一個接口,在得到主題的通知時更新自己,這個接口叫做更新接口。
* 具體觀察者(ConcreteObserver)角色:存儲與主題的狀態自恰的狀態。具體觀察者角色實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與主題的狀態 像協調。如果需要,具體觀察者角色可以保持一個指向具體主題對象的引用。
## 代碼實現
抽象主題角色類
```
public abstract class Subject {
/**
* 用來保存注冊的觀察者對象
*/
private List<Observer> list = new ArrayList<Observer>();
/**
* 注冊觀察者對象
* @param observer 觀察者對象
*/
public void attach(Observer observer){
list.add(observer);
System.out.println("Attached an observer");
}
/**
* 刪除觀察者對象
* @param observer 觀察者對象
*/
public void detach(Observer observer){
list.remove(observer);
}
/**
* 通知所有注冊的觀察者對象
*/
public void nodifyObservers(String newState){
for(Observer observer : list){
observer.update(newState);
}
}
}
```
具體主題角色類
```
public class ConcreteSubject extends Subject{
private String state;
public String getState() {
return state;
}
public void change(String newState){
state = newState;
System.out.println("主題狀態為:" + state);
//狀態發生改變,通知各個觀察者
this.nodifyObservers(state);
}
}
```
抽象觀察者角色類
```
public interface Observer {
/**
* 更新接口
* @param state 更新的狀態
*/
public void update(String state);
}
```
具體觀察者角色類
```
public class ConcreteObserver implements Observer {
//觀察者的狀態
private String observerState;
@Override
public void update(String state) {
/**
* 更新觀察者的狀態,使其與目標的狀態保持一致
*/
observerState = state;
System.out.println("狀態為:"+observerState);
}
}
```
客戶端類
```
public class Client {
public static void main(String[] args) {
//創建主題對象
ConcreteSubject subject = new ConcreteSubject();
//創建觀察者對象
Observer observer = new ConcreteObserver();
//將觀察者對象登記到主題對象上
subject.attach(observer);
//改變主題對象的狀態
subject.change("new state");
}
}
```
在運行時,這個客戶端首先創建了具體主題類的實例,以及一個觀察者對象。然后,它調用主題對象的attach()方法,將這個觀察者對象向主題對象登記,也就是將它加入到主題對象的聚集中去。
這時,客戶端調用主題的change()方法,改變了主題對象的內部狀態。主題對象在狀態發生變化時,調用超類的notifyObservers()方法,通知所有登記過的觀察者對象。
### 推模型和拉模型
在觀察者模式中,又分為推模型和拉模型兩種方式。
* 推模型
主題對象向觀察者推送主題的詳細信息,不管觀察者是否需要,推送的信息通常是主題對象的全部或部分數據。
* 拉模型
主題對象在通知觀察者的時候,只傳遞少量信息。如果觀察者需要更具體的信息,由觀察者主動到主題對象中獲取,相當于是觀察者從主題對象中拉數據。一般這種模型的實現中,會把主題對象自身通過update()方法傳遞給觀察者,這樣在觀察者需要獲取數據的時候,就可以通過這個引用來獲取了。
根據上面的描述,發現前面的例子就是典型的推模型,下面給出一個拉模型的實例。
拉模型的抽象觀察者類
拉模型通常都是把主題對象當做參數傳遞。
```
public interface Observer {
/**
* 更新接口
* @param subject 傳入主題對象,方面獲取相應的主題對象的狀態
*/
public void update(Subject subject);
}
```
拉模型的具體觀察者類
```
public class ConcreteObserver implements Observer {
//觀察者的狀態
private String observerState;
@Override
public void update(Subject subject) {
/**
* 更新觀察者的狀態,使其與目標的狀態保持一致
*/
observerState = ((ConcreteSubject)subject).getState();
System.out.println("觀察者狀態為:"+observerState);
}
}
```
拉模型的抽象主題類
拉模型的抽象主題類主要的改變是nodifyObservers()方法。在循環通知觀察者的時候,也就是循環調用觀察者的update()方法的時候,傳入的參數不同了。
```
public abstract class Subject {
/**
* 用來保存注冊的觀察者對象
*/
private List<Observer> list = new ArrayList<Observer>();
/**
* 注冊觀察者對象
* @param observer 觀察者對象
*/
public void attach(Observer observer){
list.add(observer);
System.out.println("Attached an observer");
}
/**
* 刪除觀察者對象
* @param observer 觀察者對象
*/
public void detach(Observer observer){
list.remove(observer);
}
/**
* 通知所有注冊的觀察者對象
*/
public void nodifyObservers(){
for(Observer observer : list){
observer.update(this);
}
}
}
```
拉模型的具體主題類
跟推模型相比,有一點變化,就是調用通知觀察者的方法的時候,不需要傳入參數了。
```
public class ConcreteSubject extends Subject{
private String state;
public String getState() {
return state;
}
public void change(String newState){
state = newState;
System.out.println("主題狀態為:" + state);
//狀態發生改變,通知各個觀察者
this.nodifyObservers();
}
}
```
### 兩種模式的比較
* 推模型是假定主題對象知道觀察者需要的數據;而拉模型是主題對象不知道觀察者具體需要什么數據,沒有辦法的情況下,干脆把自身傳遞給觀察者,讓觀察者自己去按需要取值。
* 推模型可能會使得觀察者對象難以復用,因為觀察者的update()方法是按需要定義的參數,可能無法兼顧沒有考慮到的使用情況。這就意味著出現新情況的時候,就可能提供新的update()方法,或者是干脆重新實現觀察者;而拉模型就不會造成這樣的情況,因為拉模型下,update()方法的參數是主題對象本身,這基本上是主題對象能傳遞的最大數據集合了,基本上可以適應各種情況的需要。
### JAVA提供的對觀察者模式的支持
在JAVA語言的java.util庫里面,提供了一個Observable類以及一個Observer接口,構成JAVA語言對觀察者模式的支持。
但一般都是用 Google的Guava類庫提供的EventBus。
## 優點
* 1、當兩個對象之間松耦合,他們依然可以交互,但是不太清楚彼此的細節。觀察者模式提供了一種對象設計,讓主題和觀察者之間松耦合。主題所知道只是一個具體的觀察者列表,每一個具體觀察者都符合一個抽象觀察者的接口。主題并不認識任何一個具體的觀察者,它只知道他們都有一個共同的接口。
* 2、觀察者模式支持“廣播通信”。主題會向所有的觀察者發出通知。
* 3、觀察者模式符合“開閉原則”的要求。
## 缺點
* 1、如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
* 2、 如果在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能導致系統崩潰。
* 3、 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎么發生變化的,而僅僅只是知道觀察目標發生了變化。
## 使用場景
* 1、一個抽象模型有兩個方面,其中一個方面依賴于另一個方面。將這些方面封裝在獨立的對象中使它們可以各自獨立地改變和復用。
* 2、一個對象的改變將導致其他一個或多個對象也發生改變,而不知道具體有多少對象將發生改變,可以降低對象之間的耦合度。
* 3、一個對象必須通知其他對象,而并不知道這些對象是誰。需要在系統中創建一個觸發鏈,A對象的行為將影響B對象,B對象的行為將影響C對象……,可以使用觀察者模式創建一種鏈式觸發機制。
## 總結
* 1、觀察者模式定義了對象之間的一對多關系。多個觀察者監聽同一個被觀察者,當該被觀察者的狀態發生改變時,會通知所有的觀察者。
* 2、觀察者模式中包含四個角色。主題,它指被觀察的對象。具體主題是主題子類,通常它包含有經常發生改變的數據,當它的狀態發生改變時,向它的各個觀察者發出通知;觀察者,將對觀察主題的改變做出反應;具體觀察者中維護一個指向具體目標對象的引用,它存儲具體觀察者的有關狀態,這些狀態需要和具體目標的狀態保持一致。
* 3、主題用一個共同的接口來更新觀察者。
* 4、觀察者與被觀察者之間用松耦合方式結合。
* 5、有多個觀察者時,不可以依賴特定的通知次序。
* 6、使用觀察者模式,可以從被觀察者處推或者拉數據。
- java
- 設計模式
- 設計模式總覽
- 設計原則
- 工廠方法模式
- 抽象工廠模式
- 單例模式
- 建造者模式
- 原型模式
- 適配器模式
- 裝飾者模式
- 代理模式
- 外觀模式
- 橋接模式
- 組合模式
- 享元模式
- 策略模式
- 模板方法模式
- 觀察者模式
- 迭代子模式
- 責任鏈模式
- 命令模式
- 備忘錄模式
- 狀態模式
- 訪問者模式
- 中介者模式
- 解釋器模式
- 附錄
- JVM相關
- JVM內存結構
- Java虛擬機的內存組成以及堆內存介紹
- Java堆和棧
- 附錄-數據結構的堆棧和內存分配的堆區棧區的區別
- Java內存之Java 堆
- Java內存之虛擬機和內存區域概述
- Java 內存之方法區和運行時常量池
- Java 內存之直接內存(堆外內存)
- JAVA內存模型
- Java內存模型介紹
- 內存模型如何解決緩存一致性問題
- 深入理解Java內存模型——基礎
- 深入理解Java內存模型——重排序
- 深入理解Java內存模型——順序一致性
- 深入理解Java內存模型——volatile
- 深入理解Java內存模型——鎖
- 深入理解Java內存模型——final
- 深入理解Java內存模型——總結
- 內存可見性
- JAVA對象模型
- JVM內存結構 VS Java內存模型 VS Java對象模型
- Java的對象模型
- Java的對象頭
- HotSpot虛擬機
- HotSpot虛擬機對象探秘
- 深入分析Java的編譯原理
- Java虛擬機的鎖優化技術
- 對象和數組并不是都在堆上分配內存的
- 垃圾回收
- JVM內存管理及垃圾回收
- JVM 垃圾回收器工作原理及使用實例介紹
- JVM內存回收理論與實現(對象存活的判定)
- JVM參數及調優
- CMS GC日志分析
- JVM實用參數(一)JVM類型以及編譯器模式
- JVM實用參數(二)參數分類和即時(JIT)編譯器診斷
- JVM實用參數(三)打印所有XX參數及值
- JVM實用參數(四)內存調優
- JVM實用參數(五)新生代垃圾回收
- JVM實用參數(六) 吞吐量收集器
- JVM實用參數(七)CMS收集器
- JVM實用參數(八)GC日志
- Java性能調優原則
- JVM 優化經驗總結
- 面試題整理
- 面試題1
- java日志規約
- Spring安全
- OAtuth2.0簡介
- Spring Session 簡介(一)
- Spring Session 簡介(二)
- Spring Session 簡介(三)
- Spring Security 簡介(一)
- Spring Security 簡介(二)
- Spring Security 簡介(三)
- Spring Security 簡介(四)
- Spring Security 簡介(五)
- Spring Security Oauth2 (一)
- Spring Security Oauth2 (二)
- Spring Security Oauth2 (三)
- SpringBoot
- Shiro
- Shiro和Spring Security對比
- Shiro簡介
- Session、Cookie和Cache
- Web Socket
- Spring WebFlux