[設(shè)計模式] 觀察者模式
手機(jī)用戶請
橫屏獲取最佳閱讀體驗,REFERENCES中是本文參考的鏈接,如需要鏈接和更多資源,可以關(guān)注其他博客發(fā)布地址。
| 平臺 | 地址 |
|---|---|
| CSDN | https://blog.csdn.net/sinat_28690417 |
| 簡書 | http://m.itdecent.cn/u/3032cc862300 |
| 個人博客 | https://yiyuery.github.io/NoteBooks/ |
觀察者模式定義了對象之間的一對多依賴,這樣依賴,當(dāng)一個對象改變狀態(tài)時,它的所有依賴者都會收到通知并更新。
對于觀察模式的實現(xiàn)方式有很多種,一般是以包含Subject和Observer接口的類設(shè)計做法最為常見。
根據(jù)多個觀察者對于同一主題信息的獲取方式,可以分為:
- 被觀察者(主題)主動推送,一變化,就將被觀察對象推送給所有觀察者。
- 觀察者通過接口主動拉取主題中的部分信息,避免無關(guān)信息的干擾。
觀察者模式的好處:
- 提供了一種對象設(shè)計,讓主題和觀察者之間松耦合
- 解決一對多的依賴問題
場景分析
加入我們現(xiàn)在有個手機(jī)工廠,每次生產(chǎn)出一批手機(jī)后,就需要通知對應(yīng)的代理商過來拿貨。
我們就可以利用觀察者模式來實現(xiàn)這個場景需求。
- 手機(jī)工廠就是被觀察對象,也就是主題。
- 多個代理商分別充當(dāng)不同的觀察者對象。需要監(jiān)聽手機(jī)工廠的
生產(chǎn)情況。 - 一旦手機(jī)工廠生產(chǎn)出手機(jī),就會通知我們的代理商過來取貨。
實戰(zhàn)
類圖

- 首先,我們需要定義觀察者和被觀察者接口,并定義通知能力接口。
public interface Observer<T> {
/**
* 觀察者接收通知
*/
void handle(T message);
}
public interface Subject<T> extends INotify{
/**
* 注冊觀察者
* @param observer
*/
void register(Observer<T> observer);
/**
* 注銷觀察者
* @param observer
*/
void cancel(Observer<T> observer);
}
public interface INotify {
/**
* 通知
*/
void sendMessage();
}
- 然后,定義對應(yīng)實例實現(xiàn)觀察者和被觀察者
手機(jī)工廠
public class PhoneFactory implements Subject<Message> {
private List<Observer<Message>> observers;
/**
* 消息體
*/
private Message message;
public PhoneFactory() {
this.observers = new ArrayList<>();
}
/**
* 注冊觀察者
*
* @param observer
*/
@Override
public void register(Observer<Message> observer) {
observers.add(observer);
}
/**
* 注銷觀察者
*
* @param observer
*/
@Override
public void cancel(Observer<Message> observer) {
observers.remove(observer);
}
/**
* 發(fā)送通知
*/
@Override
public void sendMessage() {
observers.forEach(p->{
p.handle(message);
});
}
/**
* 外部設(shè)置消息變更
* @param message
*/
public void setMessage(Message message) {
this.message = message;
sendMessage();
}
}
消息體
@Data
@Builder
@AllArgsConstructor
public class Message {
/**
* 消息體
*/
private String data;
}
代理商
//淘寶
public class TaobaoAgent implements Observer<Message> {
/**
* 觀察者接收通知
*
* @param message
*/
@Override
public void handle(Message message) {
System.out.println("Taobao receive message: " + message.getData());
}
}
//天貓
public class TianmaoAgent implements Observer<Message> {
/**
* 觀察者接收通知
*
* @param message
*/
@Override
public void handle(Message message) {
System.out.println("Tianmao receive message: " + message.getData());
}
}
- 最后,定義測試類,我們來看下效果
/**
* 自定義觀察者模式
*/
@Test
public void testX1() {
//被觀察者
PhoneFactory phoneFactory = new PhoneFactory();
//觀察者
TianmaoAgent tianmaoAgent = new TianmaoAgent();
TaobaoAgent taobaoAgent = new TaobaoAgent();
//注冊
phoneFactory.register(tianmaoAgent);
phoneFactory.register(taobaoAgent);
//發(fā)送通知
phoneFactory.setMessage(Message.builder()
.data("剛生產(chǎn)了100臺Xiaomi手機(jī)")
.build());
//移除觀察者
phoneFactory.cancel(tianmaoAgent);
//再次發(fā)送通知
System.out.println("-------------------");
phoneFactory.setMessage(Message.builder()
.data("剛又生產(chǎn)了100臺iPhone")
.build());
}
打印
Tianmao receive message: 剛生產(chǎn)了100臺Xiaomi手機(jī)
Taobao receive message: 剛生產(chǎn)了100臺Xiaomi手機(jī)
-------------------
Taobao receive message: 剛又生產(chǎn)了100臺iPhone
從輸出可以看出,觀察者和被觀察者有以下幾個特點:
- 觀察者可以隨時進(jìn)行注冊和注銷行為
- 被觀察者只會給注冊過的觀察者發(fā)送通知
- 不同代理商接收通知的順序和自己注冊的先后有關(guān)系
那么思考下這個模式有沒有什么不好的地方?
- 每次被觀察者發(fā)送消息,觀察者都需要全部接收并處理。如果信息比較復(fù)雜或是觀察者只關(guān)注其中一部分?jǐn)?shù)據(jù)怎么處理?
- 這種屬于被動等待被觀察者推的行為,如果不是通知,而是其他一些實時數(shù)據(jù)的監(jiān)控呢?如果觀察者不想等待,想直接獲取當(dāng)前實時數(shù)據(jù)信息呢?
- JDK內(nèi)置了觀察者模式的實現(xiàn),為什么還要自己定義呢?與自定義的有什么區(qū)別呢?
帶著問題,我們來看下JDK內(nèi)置的觀察者模式怎么實現(xiàn)的?并看下如何實現(xiàn)主動拉取被觀察者信息。
JDK內(nèi)置觀察者模式實現(xiàn)
public class PhoneFactory2 extends Observable {
/**
* 消息體
*/
private Message message;
public void setMessage(Message message) {
this.message = message;
setChanged();
notifyObservers(message);
}
/**
* 觀察者按需主動拉取自己所需要信息
* @return
*/
public Message getMessage() {
return message;
}
}
public class TaobaoAgent2 implements Observer {
/**
* This method is called whenever the observed object is changed. An
* application calls an <tt>Observable</tt> object's
* <code>notifyObservers</code> method to have all the object's
* observers notified of the change.
*
* @param o the observable object.
* @param arg an argument passed to the <code>notifyObservers</code>
*/
@Override
public void update(Observable o, Object arg) {
System.out.println("Taobao2 receive message: " + arg.toString());
}
}
public class TianmaoAgent2 implements Observer {
/**
* This method is called whenever the observed object is changed. An
* application calls an <tt>Observable</tt> object's
* <code>notifyObservers</code> method to have all the object's
* observers notified of the change.
*
* @param o the observable object.
* @param arg an argument passed to the <code>notifyObservers</code>
*/
@Override
public void update(Observable o, Object arg) {
System.out.println("Tianmao2 receive message: " + arg.toString());
}
}
比較下變化:
- 首先代碼量上減少了,不需要定義接口
- notifyObservers方法不用實現(xiàn),直接使用父層方法
- 使用的是繼承的方式實現(xiàn)觀察者和被觀察者
- 定義了getMessage方法,可以主動拉取信息
- 發(fā)送通知前,需要調(diào)用父層方法setChanged
定義下測試類:
/**
* 使用JDK原生觀察者模式
*/
@Test
public void testX2() {
//被觀察者
PhoneFactory2 phoneFactory = new PhoneFactory2();
//觀察者
TianmaoAgent2 tianmaoAgent = new TianmaoAgent2();
TaobaoAgent2 taobaoAgent = new TaobaoAgent2();
//注冊
phoneFactory.addObserver(tianmaoAgent);
phoneFactory.addObserver(taobaoAgent);
//發(fā)送通知
phoneFactory.setMessage(Message.builder()
.data("剛生產(chǎn)了100臺Xiaomi手機(jī)")
.build());
//移除觀察者
phoneFactory.deleteObserver(tianmaoAgent);
//再次發(fā)送通知
System.out.println("-------------------");
phoneFactory.setMessage(Message.builder()
.data("剛又生產(chǎn)了100臺iPhone")
.build());
//主動拉取消息
System.out.println("pull message:" + phoneFactory.getMessage());
}
看下測試類的輸出:
Taobao2 receive message: Message(data=剛生產(chǎn)了100臺Xiaomi手機(jī))
Tianmao2 receive message: Message(data=剛生產(chǎn)了100臺Xiaomi手機(jī))
-------------------
Taobao2 receive message: Message(data=剛又生產(chǎn)了100臺iPhone)
pull message:Message(data=剛又生產(chǎn)了100臺iPhone)
分析下兩次輸出的區(qū)別:
- 接收消息的順序反了
- 被觀察者開放了自身被監(jiān)控屬性的Getter方法
其實乍看之下,兩種方式實現(xiàn)結(jié)果貌似沒什么區(qū)別,但是仔細(xì)觀察JDK內(nèi)置的Observable,不是一個接口,是個類。其內(nèi)部的setChanged方法也是被protected修飾的
protected synchronized void setChanged() {
changed = true;
}
這意味著:你除非繼承自O(shè)bservable,否則無法創(chuàng)建Observable實例,并組合到你自己的對象中來,這個設(shè)計違反了多用組合,少用繼承的設(shè)計原則。
相較于我們自己實現(xiàn)的觀察者模式,可以結(jié)合泛型定義被通知的消息體,還可以結(jié)合我們的通知接口INotify等方式來擴(kuò)展,開發(fā)自由度更加高。但是無論是哪種方式,關(guān)鍵是熟悉觀察者模式后,善用即可,
REFERENCES
《Head First》讀書筆記
更多
掃碼關(guān)注
架構(gòu)探險之道,回復(fù)文章標(biāo)題,獲取本文相關(guān)源碼和資源鏈接
知識星球(掃碼加入獲取歷史源碼和文章資源鏈接)