概述
是不是你平時安裝程序的時候都會讓你選擇是否訂閱通知,或者我們在網上買東西的物流過程中,每到一個新的進度點都會進行更新,以及通知。其實這裏就用到了我們的觀察者模式。
概念
觀察者模式:定義對象間的一種一對多的依賴關係。當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並自動更新。
比如我們對於天氣的訂閱,是不是當我們的天氣進行變化的時候,應用就會發出通知,告知我們天氣的變化。圖中的Subject就是天氣,Observer就是用戶。當天氣進行變化的時候Subject中的Notify方法就會被調用,這樣Observer中的Update方法就會做出相應的更新。而下面的兩個分別是上面的實現類。
觀察者模式是單向依賴的,觀察者對象的狀態是依賴於目標對象的,只有目標對象的狀態發生改變,纔會改變觀察者對象的狀態。
觀察者模式主要有兩種實現方法:推模型和拉模型。
- 推模型:
目標對象主動向觀察者推送目標的詳細信息,推送的信息通常是目標對象的全部或者部分數據。
- 拉模型:
目標對象在通知觀察者的時候只傳遞少量信息。如果觀察者需要更具體的信息,由觀察者主動到目標對象中獲取,相當於是觀察者從目標對象中拉數據,這種實現中一般會把目標對象通過Update方法傳遞給觀察者。
觀察者模式實現代碼
手動實現
我們根據類圖可以知道觀察者模式主要是由4個類,Subject,Observer,ConcreteSubject,ConcreteObserver。在Subject中我們需要定義一個集合來儲存我們的觀察者對象,並且在其中要實現觀察者的添加以及刪除,並且要完成通知所有註冊的觀察者對象的方法。
Subject.java(推)
package com.xjh.observer;
import java.util.ArrayList;
import java.util.List;
/**
* 目標對象,它知道觀察它的觀察者,並提供註冊和刪除觀察者的接口
* @author Gin
*
*/
public class Subject {
//保存註冊的觀察者對象
private List<Observer> observers = new ArrayList<Observer>();
//添加觀察者
public void attach(Observer observer) {
observers.add(observer);
}
//刪除觀察者
public void detach(Observer observer) {
observers.remove(observer);
}
//通知所有註冊的觀察者對象
protected void notifyObservers(String context) {
for(Observer observer : observers) {
observer.update(context);
}
}
}
Subject.java(拉)
package com.xjh.observer;
import java.util.ArrayList;
import java.util.List;
/**
* 目標對象,它知道觀察它的觀察者,並提供註冊和刪除觀察者的接口
* @author Gin
*
*/
public class Subject {
//保存註冊的觀察者對象
private List<Observer> observers = new ArrayList<Observer>();
//添加觀察者
public void attach(Observer observer) {
observers.add(observer);
}
//刪除觀察者
public void detach(Observer observer) {
observers.remove(observer);
}
//通知所有註冊的觀察者對象
protected void notifyObservers() {
for(Observer observer : observers) {
observer.update(this);
}
}
}
然後定義觀察者接口,裏面擁有Update方法
Observer.java(推)
package com.xjh.observer;
/**
* 這是一個觀察者接口,定義一個更新的接口給那些在目標發生改變的時候被通知的對象
* @author Gin
*
*/
public interface Observer {
/**
* 更新接口
* @param subject 傳入對象目標,方便獲取相應的目標對象的狀態
*/
public void update(String context);
}
Observer.java(拉)
package com.xjh.observer;
/**
* 這是一個觀察者接口,定義一個更新的接口給那些在目標發生改變的時候被通知的對象
* @author Gin
*
*/
public interface Observer {
/**
* 更新接口
* @param subject 傳入對象目標,方便獲取相應的目標對象的狀態
*/
public void update(Subject subject);
}
之後就要編寫實現類,在Subject的實現類中我們需要定義狀態,用來更新,在set方法中調用父類的notifyObservers方法進行對觀察者的通知。
ConcreteSubject.java(推)
package com.xjh.observer;
/**
* 具體的目標對象,負責把有關狀態存人到相應的觀察者對象中
* @author Gin
*
*/
public class ConcreteSubject extends Subject {
//目標對象狀態
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
this.notifyObservers(subjectState);
}
}
ConcreteSubject.java(拉)
package com.xjh.observer;
/**
* 具體的目標對象,負責把有關狀態存人到相應的觀察者對象中
* @author Gin
*
*/
public class ConcreteSubject extends Subject {
//目標對象狀態
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
this.notifyObservers();
}
}
然後我們在Observer的實現類中實現Update方法就好了。
ConcreteObserver.java(推)
package com.xjh.observer;
/**
* 具體的觀察者對象,實現更新的方法,使自身狀態和目標的狀態保持一致
* @author Gin
*
*/
public class ConcreteObserver implements Observer {
//觀察者的狀態
private int id;
private String observerState;
public ConcreteObserver(int id) {
this.id = id;
}
/**
* 獲取目標類的狀態同步到觀察者的狀態
*/
@Override
public void update(String context) {
observerState = context;
System.out.println(id+":"+observerState);
}
}
ConcreteObserver.java(拉)
package com.xjh.observer;
/**
* 具體的觀察者對象,實現更新的方法,使自身狀態和目標的狀態保持一致
* @author Gin
*
*/
public class ConcreteObserver implements Observer {
//觀察者的狀態
private int id;
private String observerState;
public ConcreteObserver(int id) {
this.id = id;
}
/**
* 獲取目標類的狀態同步到觀察者的狀態
*/
@Override
public void update(Subject subject) {
observerState = ((ConcreteSubject)subject).getSubjectState();
System.out.println(id+":"+observerState);
}
}
當然我們怎麼去使用觀察者模式呢,這裏就要編寫一個測試類:
Client.java
package com.xjh.observer;
public class Client {
public static void main(String[] args) {
//創建目標
ConcreteSubject subject = new ConcreteSubject();
//創建觀察者
ConcreteObserver one = new ConcreteObserver(1);
ConcreteObserver two = new ConcreteObserver(2);
//註冊觀察者
subject.attach(one);
subject.attach(two);
//目標發佈天氣
subject.setSubjectState("明天開會");
//刪除觀察者
subject.detach(one);
//目標發佈天氣
subject.setSubjectState("後天開會");
}
}
運行結果如下,滿足了我們的要求。
使用Java提供的接口實現
之前爲了更好的理解觀察者模式,我們已經實現了基本功能,當時Java其實已經提供好了觀察者模式的實現。我們先說下使用Java實現與我們自己實現的不同吧。
- 不需要再定義觀察者和目標接口
- 具體的目標實現裏面不需要再維護觀察者的註冊信息,這個在Java中的Observable類裏面實現好了。
- 觸發通知的方式發生改變,需要先調用setChanged方法,這個是Java爲了幫助實現更精確的觸發控制而提供的功能。
- 具體觀察者的實現裏面,update方法其實已經能同時支持推模型和拉模型
我們使用Java所提供的接口實現的好處就是更加的規範。
我們實現Observable接口,裏面已經實現了添加,刪除以及通知的方法。
ConcreteSubject.java
package com.xjh.jdkobserver;
import java.util.Observable;
/**
* 天氣目標具體實現類
* @author Gin
*
*/
public class ConcreteSubject extends Observable {
private String context;
public String getContext() {
return context;
}
public void setContext(String context) {
this.context = context;
//通知所有觀察者
this.setChanged();
this.notifyObservers(context);//推模型
//this.notifyObservers();//拉模型
}
}
因爲Observable實現了推拉模型的結合,所以在重寫update方法裏面有兩個參數,分別代表的就是更新內容,以及Observable對象的引用。
ConcreteObserver.java
package com.xjh.jdkobserver;
import java.util.Observable;
import java.util.Observer;
public class ConcreteObserver implements Observer {
//觀察者ID
private int id;
public ConcreteObserver(int id) {
this.id = id;
}
@Override
public void update(Observable arg0, Object arg1) {
//推模型,推送過來消息
System.out.println(id+":"+arg1);
//拉模型,主動到目標對象中去拉
//System.out.println(id+":"+((ConcreteSubject)arg0).getContext());
}
}
然後我們在 Main函數中按照步驟實現就好了。
Client.java
package com.xjh.jdkobserver;
public class Client {
public static void main(String[] args) {
//創建目標
ConcreteSubject subject = new ConcreteSubject();
//創建觀察者
ConcreteObserver one = new ConcreteObserver(1);
ConcreteObserver two = new ConcreteObserver(2);
ConcreteObserver three = new ConcreteObserver(3);
//註冊觀察者
subject.addObserver(one);
subject.addObserver(two);
subject.addObserver(three);
//目標發佈天氣
subject.setContext("明天開會");
//刪除觀察者
subject.deleteObserver(one);
//目標發佈天氣
subject.setContext("後天開會");
}
}
我們可以發現他的排序是321,但是我們add的順序是123,所以可以發現他的插入和讀取是類似與棧的形式(我猜的)。
總結
推模型是假定目標對象知道觀察者需要的數據,推模型可能會使觀察者對象難以複用。
拉模型是目標對象不知道觀察者具體需要什麼數據,因此把自身傳給觀察者,由觀察者來取值,然而在拉模型下,update方法的參數是目標對象本身,基本上可以適應各種情況的需要。
適用場景
- 當一個抽象模型有兩個方面,其中一個方面的操作依賴於另一個方面的狀態變化
- 如果在更改一個對象的時候,需要同時連帶改變其他的對象,而且不知道究竟應該有多少對象需要被連帶改變
- 當一個對象必須通知其他的對象,但是你又希望這個對象和其他的被通知的對象是鬆散耦合的
優點
- 觀察者模式實現了觀察者和目標之間的抽象耦合,解除了實現耦合
- 觀察者模式實現了動態聯動
- 觀察者模式支持廣播通知