【pattern】設計模式(3) - Observer觀察者模式
源碼地址:https://github.com/vergilyn/design-patterns
另外一個大神很全的Github:https://github.com/iluwatar/java-design-patterns
1. 概述
觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。當這個主題對象在狀態發生變化時,可以通知所有觀察者對象,使它們(觀察者對象)能夠自動更新自己。
比如,微信用戶就是觀察者,微信公眾號就是被觀察者(主題對象),當公眾號更新時會通知所有的觀察者。
以上示例更形象的表述可能是:發布/訂閱(publish/subscribe)。
(個人覺得)觀察者模式的核心:其實就是觸發及響應。所以感覺跟"責任鏈模式"有一定程度的類似,可能"責任鏈模式"的適用場景更加具體,而"觀察者模式"相對就抽象很多。
2. 適用場景
關聯行為場景,需要註意的是,關聯行為是可拆分的,而不是“組合”關系。
即一個抽象模型有兩個方面,其中一個方面依賴於另一個方面。將這些方面封裝在獨立的對象中使它們可以各自獨立地改變和復用。事件多級觸發場景。
即一個對象的改變將導致其他一個或多個對象也發生改變,而不知道具體有多少對象將發生改變,可以降低對象之間的耦合度。跨系統的消息交換場景,如消息隊列、事件總線的處理機制。
(事件總線)一個對象必須通知其他對象,而並不知道這些對象是誰;
需要在系統中創建一個觸發鏈,A對象的行為將影響B對象,B對象的行為將影響C對象……,可以使用觀察者模式創建一種鏈式觸發機制。
這些是我在網上亂七八糟看到的適用場景,能理解的很好理解,不能理解的很頭疼。比如在看某些源碼時,感覺像是observer-pattern,但又感覺不是...
3. 類圖
涉及到角色:
- AbstractSubject:抽象主題;
- Subject:具體主題(被觀察者);重點,會用一個集合List|Map|Set來保存所有的觀察者。
- AbstractObserver:抽象觀察者;
- Observer:具體觀察者;
註意:觀察者observer與被觀察者subject是相對的,某些情況下可能兩者都是。
之所以要定義AbstractSubject、 AbstractObserver,主要在於對開發原則(SOLID)的考慮,解除耦合及方便擴展。
(看到有blog總結說Observer滿足"開閉原則",我對SOLID理解不是那麽深,只是有體會應該要依賴抽象/接口,所以才會定義AbstractSubject、 AbstractObserver)。
interface AbstractSubject
,所以我把observers
定義到了Subject
。視實際情況可能會定義為abstract class AbstractSubject
,然後把observers
定義在其中,所有AbstractSubject
的子類共享observers
.4. 最基本的示例代碼
場景: subject[微信公眾號],observer[微信用戶]。當公眾號更新時,將更新信息[message]通知所有的觀察者。
特別:observer-pattern可以分為"推模型"、"拉模型",示例代碼是"推模型"。
/** 抽象主題 */
public interface AbstractSubject {
String name();
void add(AbstractObserver observer);
void del(AbstractObserver observer);
void publishMessage(String message);
}
/** 抽象觀察者 */
public interface AbstractObserver {
String name();
void onMessage(AbstractSubject subject, String msg);
}
/** 具體主題(被觀察者)*/
public class Subject implements AbstractSubject {
private final Set<AbstractObserver> observers = Sets.newLinkedHashSet();
private final String name;
public Subject(String name) {
this.name = name;
}
@Override
public String name() {
return name;
}
@Override
public void add(AbstractObserver observer) {
observers.add(observer);
}
@Override
public void del(AbstractObserver observer) {
observers.remove(observer);
}
@Override
public void publishMessage(String message) {
for (AbstractObserver observer : observers){
observer.onMessage(this, message);
}
}
}
/** 具體觀察者 */
public class Observer implements AbstractObserver {
private final String name;
public Observer(String name) {
this.name = name;
}
@Override
public String name() {
return name;
}
public void onMessage(AbstractSubject subject, String msg) {
System.out.println(String.format("observer[%s]: subject[%s], message[%s]", name, subject.name(), msg));
}
}
public class TestObserver {
public static void main(String[] args) {
AbstractSubject subject = new Subject("公眾號-01");
AbstractObserver observer1 = new Observer("user-01");
AbstractObserver observer2 = new Observer("user-02");
AbstractObserver observer3 = new Observer("user-03");
subject.add(observer1);
subject.add(observer2);
subject.add(observer3);
subject.publishMessage("some message");
}
}
輸出結果:
observer[user-01]: subject[公眾號-01], message[some message]
observer[user-02]: subject[公眾號-01], message[some message]
observer[user-03]: subject[公眾號-01], message[some message]
5. 註意事項
(開發效率)雖然observer-pattern代碼看起來很簡單,但如果用於實際
業務邏輯代碼,非框架級代碼
,用observer-pattern會讓開發、調試、閱讀等內容變得比較復雜。(運行效率)如果一個subject有很多直接和間接的observer的話,將所有的observers都通知到會花費很多時間。
解決:
視具體情況,更多可能采用異步
的方式通知各個observer。避免observer之間相互的影響。
且要註意若通知某個observer發生exception時,subject要怎麽處理?是由subject處理還是由observer處理?(循環依賴)如果在observer和subject之間有循環依賴的話,註意避免循環調用,否則會導致系統崩潰。
(缺點)observer-pattern沒有相應的機制讓observer知道subject是怎麽發生變化的,而僅僅只是知道subject發生了變化。
個人並不理解,感覺有辦法能讓object知道subject是怎麽發生變化的啊。抉擇:是用"推模式",還是"拉模式"。
6. "推模型"、"拉模型"
個人認同的"推模型"、"拉模型"說法:
"推模型":subject維護一份observers的列表,每當有更新發生,subject會把需要的參數主動推送到各個observer。
"拉模型": observer維護自己所關心的subjects列表,自行決定在合適的時間(延時性)去subjects獲取相應的更新數據。
我最初也沒扯清楚"推模型"、"拉模型"的區別。然後去看了幾篇blog,發現有兩種說法,有種說法跟我最初理解的一樣,但當我看見另外一種說法時,我發現更能接受"另外"的這種說法。
"推模型"、"拉模型"的區別是:如果subject是主動的一方,則是"推模型";反之,若observer是主動方,則是"拉模型"。
"推模型",如上面代碼示例,是由subject維護了一份observers,由subject主動把消息推送給observer。
而如果是"拉模型",則是反過來,由observer維護一份subjects,並且是有observer定時去獲取subject的信息。
(兩種說法的參考blog)
個人認為:錯誤的理解
參考:http://blog.csdn.net/happyever2012/article/details/44678595
這邊文章中所的"推"、"拉"跟我最初理解的一樣, 但我一直感覺他們實質都是一樣的。
當subject通知observer時,不管傳需求的部分參數,還是把subject本身傳遞給observer,感覺都像"推"。所以,我就繼續在網上找別的blog,找到了我認同的說法。個人認為:正確的理解
參考:http://raychase.iteye.com/blog/1337015
6.1. "推模型"、"拉模型"的各自特點
"推模型"的特點:
1) 精準。如果沒有更新發生,不會有任何更新消息推送的動作,即每次消息推送都發生在確確實實的更新事件之後。
2) 實時。事件發生後的第一時間即可觸發通知操作。
3) 可以表達出不同事件發生的先後順序。保證了是按事件改變順序通知observer。
4) 因為發起方是subject,所以要在subject方指定通知的時間,避開subject自身或observer方的繁忙時段。
"拉模型"的特點:
1) 延時性。因為是定時
去請求subject的信息,所以有延時性。
2) (不知道怎麽描述)同樣因為定時
,如果長時間subject沒變化,observer還是會按時去更新subject,做是否更新判斷,無意義的請求獲取subject。
3) 因為發起方是observer,所以要在observer方指定更新的時間,避開observer自身或subject方的繁忙時段。
這裏先要明白一個概念,subject一般被認為是服務端,observer則是客戶端。所以有些可能subject與observer根本不是同一個服務(不在同一套代碼內)。
所以,如果需由observer自己控制自己的更新時間,那可以考慮選擇"拉模型"。反之,若subject覺得這時間段subject服務器壓力太大,又不是一定要馬上通知各個observer,則"推模式"更容易在subject控制更新。
6.2. "推模型"、"拉模型"註意事項
1) 均無法保證事件都會響應
"推模型"中,若當subject通知observer時,某個observer客戶端已經崩潰,那麽要考慮subject服務端是不是要記錄這次失敗,重新通知這個observer直到成功。
"拉模型"中,因為延時性,subject快速的狀態改變1 -> 2 -> 3
,observer定時獲取的值是3
,那麽要考慮observer是不是要獲取完整的狀態變化過程執行相應的邏輯代碼。
2) "權限控制"交給誰?
(這可能無關"推模型"、"拉模型") 比如,"推模式"subject會把所有的觸發事件都通知給全部observers,至於這個事件各個observer是不是需要處理,各個observer自己去考慮。(當然,代碼完全可以寫成是在服務端subject控制某個事件不通知某些observer)。
可能類似的場景如,subject服務端提供了很多免費的服務,observer客戶端要用哪些自己考慮。subject服務端提供了一些收費服務,此時不管是"推模型"還是"拉模型"肯定都只能在subject服務端控制這個權限。
3) 壓力交給誰去承受?
比如,若observers數據量相當龐大,subject維護這份數據壓力很大(或全部通知到壓力很大),則可以把關系解脫到observer去完成,即選擇"拉模型"。
又或者,subject服務端覺得被一堆observer定時請求壓力較大,則選擇"推模型"。
以上很多都可能是我多此一舉的瞎考慮, 其實這點性能差別根本不用考慮。
暫時感覺比較明顯的一點,是不是要保證實時性。
實際中我看的較多都是"推模型","拉模型"少很多。比較明顯的"拉模型"就是互聯網頁面的訪問,
7. 擴展: 觀察者模式 vs 責任鏈模式
暫時還沒有仔細看"責任鏈模式",而且只是個人感覺"責任鏈模式"和"觀察者模式"很相似,都是基於"因為某個東西被觸發,所以需要執行操作"。只是"責任鏈模式"更具體,"觀察者模式"的使用場景更抽象。
參考:
1. https://www.cnblogs.com/cbf4life/archive/2009/12/29/1634773.html
以上blog並沒有讓我明白他們之間的區別。
2. http://bbs.csdn.net/topics/330020444
可以參考,最後提到的"推模型"與"拉模型"比較有意義。但其余的回答我還是沒理解。
8. 實際場景: Redisson源碼中的observer-pattern(publish/subscribe)
場景簡述:redisson是用redis實現分布式並發鎖,當多個線程競爭同一把鎖時,只會有一個線程會獲得鎖,此時其余線程都在等待獲取鎖。當持有鎖線程執行完並釋放鎖時,需要通知其余線程去競爭獲取鎖。此時就是可以用publish/subscribe。
線程A先去嘗試獲取鎖,發現已被其它線程X持有,此時線程A就subscribe競爭該鎖的subject。當線程X釋放鎖時,及時publish通知所有的observer去競爭鎖。(實際redisson中並沒有通知全部的observer,而是只通知隊列中的第一個競爭線程,讓其獲得鎖。)
本來打算把redisson的相關源碼貼出來,但略多,而且要解釋的太多了。等寫redisson源碼閱讀再說吧。
【pattern】設計模式(3) - Observer觀察者模式