文章目錄
Java —— Observer 觀察者模式
行爲型模式
當對象存在一對多時,使用觀察者模式(Observer Pattern)。 一個對象被修改時,則會自動通知它的依賴對象;
發送狀態變化通知;
簡介
Observer “進行觀察的人”,即“觀察者”的意思。
在觀察者模式中,當觀察對象的狀態發生變化時,會通知給觀察者。Observer 模式適用於根據對象狀態進行相應處理的場景。
定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。
用處
一個對象狀態改變給其他對象通知的問題,而且要考慮到易用和低耦合,保證高度的協作。
簡單例子
類名 | 說明 |
---|---|
Observer | 表示觀察者的接口 |
NumberGenerator | 表示生成數值的對象的抽象類 |
RandomNumberGenerator | 生成隨機數類 |
DigitObserver | 表示以數字形式顯示數值的類 |
GraphObserver | 表示以簡單的圖示形式顯示數值的類 |
Main | 測試程序 |
- Observer 接口
“觀察者”接口,具體觀察者會實現這個接口。
在此,這個Observer接口的作用是,便於我們理解Observer示例程序而編寫的,它與Java類庫中的java.util.Observer接口不同。
用於生成數值的NumberGenerator類會調用update方法。Generator有“生成器”、“產生器”的意思。
調用update方法,NumberGenerator類就會將“生成的數值發生變化,請更新顯示內容”的通知發給Observer。
public interface Observer {
public abstract void update (NumberGenerator generator);
}
- NumberGenerator
用於生成數值的抽象類。
生成數值的方法(execute方法)和獲取數值的方法(getNumber方法)都是抽象方法,需要子類去實現。
observer字段中保存有觀察NumberGenerator的Observer們。
addObserver 方法用於註冊Observer,而deleteObserver方法用於刪除Observer。
notifyObservers方法會向所有的Observer發送通知,告訴它們“我生成的數值發生了變化,請更新顯示內容”。該方法會調用每個Observer的update方法。
public abstract class NumberGenerator {
/**
* 保存所有Observer
*/
private ArrayList<Observer> observers = new ArrayList();
/**
* 註冊observer
* @param observer
*/
public void addObserver(Observer observer) {
observers.add(observer);
}
/**
* 刪除Observer
* @param observer
*/
public void deleteObserver(Observer observer) {
observers.remove(observer);
}
/**
* 向所有Observer發送通知
*/
public void notifyObservers() {
Iterator it = observers.iterator();
while ( it.hasNext()) {
Observer observer = (Observer) it.next();
observer.update(this);
}
}
/**
* 獲取數值
* @return
*/
public abstract int getNumber();
/**
* 生成數值
*/
public abstract void execute();
}
- RandomNumberGenerator
NumberGenerator的子類,會生成隨機數。
random是java.util.Random的實例(隨機數生成器)。number保存當前生成的隨機數。
getNumber方法用於獲取number字段的值。
execute方法會生成20個隨機數(0 ~ 49的整數),並通過notifyObservers方法把每次生成結果通知觀察者。
這裏使用的nextInt方法是java.util.Random類的方法,它的功能是返回下一個隨機整數值(取值範圍大於0,小於指定值)。
public class RandomNumberGenerator extends NumberGenerator {
/**
* 隨機數生成器
*/
private Random random = new Random();
/**
* 當前數值
*/
private int number;
/**
* 獲取當前數值
* @return
*/
@Override
public int getNumber() {
return number;
}
@Override
public void execute() {
for(int i=0; i<20; i++) {
number = random.nextInt(50);
notifyObservers();
}
}
}
- DigitObserver
實現Observer接口,功能是以數字形式顯示觀察到的數值。
update方法接收NumberGenerator的實例作爲參數,然後通過調用NumberGenerator類的實例的getNumber方法可以可以獲取當前數值,並輸出這個數值。
爲了看清它是如何顯示數值的,這裏使用Tread.sleep來降低了程序運行速度。
public class DigitObserver implements Observer {
@Override
public void update(NumberGenerator generator) {
System.out.println("DigitObserver : " + generator.getNumber());
try{
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- GraphObserver
實現Observer接口。該類會將觀察到的數值以 ***** 這樣的簡單圖示的形式顯示出來;
public class GraphObserver implements Observer {
@Override
public void update(NumberGenerator generator) {
System.out.println(" ==== GraphObserver ===");
int count = generator.getNumber();
for(int i=0; i<count; i++) {
System.out.print("*");
}
System.out.println("");
try {
Thread.sleep(100);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
- Main
測試類,生成一個RandomNumberGenerator類的實例和兩個觀察者,其中observer1 是DigitObserver類的實例,observer2 是 GraphObserver類的實例。
在使用addObserver註冊觀察者後,它還會調用generator.execute()方法生成隨機數。
public class Main {
public static void main(String[] args) {
// 通過觀察角色獲取具體的觀察者
NumberGenerator generator = new RandomNumberGenerator();
Observer observer1 = new DigitObserver();
Observer observer2 = new GraphObserver();
// 通過addObserver方法註冊多個Observer
generator.addObserver(observer1);
generator.addObserver(observer2);
// 執行具體觀察者execute函數,向所有的Observer發送通知
generator.execute();
}
}
- 運行結果
DigitObserver : 42
==== GraphObserver ===
******************************************
DigitObserver : 25
==== GraphObserver ===
*************************
DigitObserver : 24
==== GraphObserver ===
************************
DigitObserver : 23
==== GraphObserver ===
***********************
DigitObserver : 41
==== GraphObserver ===
*****************************************
DigitObserver : 27
==== GraphObserver ===
***************************
DigitObserver : 12
==== GraphObserver ===
************
DigitObserver : 42
==== GraphObserver ===
******************************************
DigitObserver : 26
==== GraphObserver ===
**************************
DigitObserver : 18
==== GraphObserver ===
******************
DigitObserver : 40
==== GraphObserver ===
****************************************
DigitObserver : 45
==== GraphObserver ===
*********************************************
DigitObserver : 20
==== GraphObserver ===
********************
DigitObserver : 33
==== GraphObserver ===
*********************************
DigitObserver : 23
==== GraphObserver ===
***********************
DigitObserver : 34
==== GraphObserver ===
**********************************
DigitObserver : 47
==== GraphObserver ===
***********************************************
DigitObserver : 32
==== GraphObserver ===
********************************
DigitObserver : 15
==== GraphObserver ===
***************
DigitObserver : 31
==== GraphObserver ===
*******************************
- Main時序圖
涉及角色
-
Subject(觀察角色)
表示觀察對象。定義註冊觀察者和刪除觀察者的方法。
此外,它還聲明瞭“獲取現在的狀態”的方法。
示例中,由NumberGenerator代表此角色。 -
ConcreteSubject(具體的觀察對象)
表示具體的被觀察對象。自身狀態發生變化後,會通知素有已註冊的Observer角色。
示例中,RandomNumberGenerator代表此角色。 -
Observer(觀察者)
負責接收來自 Subject 角色的狀態變化通知。
爲此,聲明瞭update方法。
示例中,由Observer接口代表此角色。 -
ConcreteObserver(具體的觀察者)
表示具體的Observer。當update方法被調用後,會去獲取觀察的對象的最新狀態。
示例中,由DigitObserver類和GraphObserver類代表此角色。
要點
這裏也出現了可替換性
- 利用抽象類和接口從具體類中抽出抽象方法。
- 在將實例作爲參數傳遞至類中,或者在類的字段中保存實例時,不使用具體類型,而是使用抽象類型和接口。
Observer的順序
當Observer的行爲會對Subject產生影響時
傳遞更新信息的方式
從“觀察”變爲“通知”
MVC(Model / View / Controller)
相關的設計模式
- Mediator 中介者模式
應用實例
- 拍賣
拍賣師觀察最高標價,然後通知給其他競拍者競價。 - 比喻
西遊記,孫悟空請菩薩降服紅孩兒,菩薩灑一地水找來一隻烏龜,烏龜就是觀察者,他觀察菩薩灑水的動作。
優點
- 觀察者和被觀察者是抽象耦合的。
- 建立一套觸發機制。
缺點
- 一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
- 如果在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能導致系統崩潰。
- 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。
使用場景
- 一個抽象模型有兩個方面,其中一個方面依賴於另一個方面。將這些方面封裝在獨立的對象中使它們可以各自獨立地改變和複用。
- 一個對象的改變將導致其他一個或多個對象也發生改變,而不知道具體有多少對象將發生改變,可以降低對象之間的耦合度。
- 一個對象必須通知其他對象,而並不知道這些對象是誰。
- 需要在系統中創建一個觸發鏈,A對象的行爲將影響B對象,B對象的行爲將影響C對象……,可以使用觀察者模式創建一種鏈式觸發機制。
注意事項
- JAVA 中已經有了對觀察者模式的支持類。
- 避免循環引用。
- 如果順序執行,某一觀察者錯誤會導致系統卡殼,一般採用異步方式。