時間:公元前716年前後(西周)
地點:鎬京驪山烽火臺
典故:烽火戲諸侯,褒姒一笑失天下
事件:周宣王死後,其子宮涅繼位,是爲周幽王。適逢關中一帶發生地震,加以連年旱災,百姓飢寒交迫,民不聊生。然周幽王是個荒淫無道的昏君,整日縱情於聲色犬馬之中,醉生夢死。有大丞褒珦勸諫,反受牢獄之災三年。褒珦族人爲救褒珦,進獻美女褒姒。話說這褒姒,可謂名不虛傳的絕色佳女。細柳腰,冰肌玉膚、花顏妖嬈、色眼微動,便可令人魂不守舍。但褒姒雖生沉魚落雁閉月羞花之貌,卻冷若冰霜,自進宮以來從未笑過一次。這可急的周幽王啊,下令誰能博美人一笑必有重賞。佞臣虢石父獻計,立褒珦之子爲太子,並於驪山烽火臺點燃烽火,讓王妃看千軍萬馬壯觀場面,必笑顏常開。昏君周幽王聽後即令詔書廢申皇后和太子宜臼,立褒姒爲皇后,伯服爲太子,即駕幸驪山。幽王來到驪山溫泉行宮,命令點燃烽火,但見狼煙四起,火光沖天,各路諸侯看見烽火,急忙調動三軍,直奔驪山。近前卻聽樓閣裏,琴瑟聲聲,觥籌交錯,卻不見一兵一卒,可此時的褒姒看見驚恐萬狀的各路諸侯,卻嫣然一笑,幽王見褒姒嫵媚萬千,不覺欣喜若狂,於是重賞虢石父。
結果:幽王每次點燃烽火都沒有事,時間久了諸侯就不再派兵了,最終犬戎兵攻破鎬京,於驪山殺幽王,西周滅亡。(此典故今被證明是杜撰)
時間:1980年代某月某日
地點:某鄉某村
事件:傍晚4點,村口大廣播響起了鄉廣播站播音員小芳的聲音:XX村XX生產隊,XX村XX生產隊,現通知你們晚飯後7點半在村口場地上召開臨時會議,關於生產隊秋收問題,請大家務必準時參加。
結果:晚上7點20,村場地上已經聚滿了人,大人端着小板凳坐着閒聊等開會,小孩在場地上追逐打鬧,貓兒叫狗兒跳,場地上一片喧囂。
時間:2019年隨時
地點:隨地
事件:在我們的微信裏,每個人都或多或少關注一些訂閱號,每天只要這些訂閱號發表文章,我們都會第一時間收到推送。類似的還有你的微博關注裏,只要你接收通知,那麼你關注的藝人或者親友只要發表動態,你也能隨時瀏覽到。
結果:不管訂閱號還是微博,覺得內容吸引你就關注,就會收到推送。覺得不好看就取消關注,就不再收到推送。
上面的三個小故事,是古人、父輩和我們這一代人的生活截然不同的寫照,但是不管哪個時代,我們獲取消息的方式都有那麼一些異曲同工之處,是智慧的延續,也是科技的進步。無論是烽火臺、村廣播、訂閱號還是微博,都有着消息通知者的角色,而各諸侯、父輩們、我們都有着觀察者的角色,我們根據收到的通知做不同的事情。接下來就進入我們今天的主題,Java設計模式之——觀察者模式。
什麼是觀察者模式
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
觀察者模式(Observer Pattern):在對象之間定義一對多依賴關係,以便當一個對象更改狀態時,將自動通知和更新其所有依賴關係。
類似上面的故事裏,烽火臺和諸侯,廣播和村民,訂閱號和關注者,都是一對多的依賴關係,一改變並通知給多,多收到通知會做出相應動作,這就是觀察者模式的核心。觀察者模式屬於行爲型模式,在項目中也經常使用到。
觀察者模式四個角色
本質上觀察者模式只有主題(Subject)和觀察者(Observer)兩個角色,但是在實際使用中我們通常會抽象出四個角色,分別是:
-
抽象主題(Subject):又叫目標,是被觀察對象,包含所有觀察者對象的引用,通常是接口或者抽象類,提供動態的增加、移除觀察者的方法,同時定義了狀態改變時通知所有觀察者的通知方法。
-
具體主題(ConcreteSubject):保存具體發生改變的狀態數據,在內部狀態改變時,給所有的註冊的觀察者發出通知。(具體主題實現了抽象主題中定義的抽象業務邏輯方法,如果無需實現,具體主題類可以省略。)
-
抽象觀察者(Observer):定義觀察者在收到主題狀態改變的通知時的一個更新方法,通常是一個接口。
-
具體觀察者(ConcreteObserver):實現抽象觀察者接口,每個具體觀察者收到通知後,根據自己不同的業務邏輯去更新自己。
觀察者模式UML圖
代碼示例前言(女神初現)
當開頭想到烽火臺和父輩們那個年代的時候,記憶的缺口越開越大,那些美好的,悲傷的回憶都湧上心頭,索性這篇就賣一下情懷。看到這兒的朋友,如果你還是單身,並且有了想追求的對象,那麼你的福利來了,因爲我忍着痛回憶我當初是怎麼和我的女神擦肩而過的,眼看着她投進別人的懷抱,並將這慘痛的教訓寫出來,通過我的前車之鑑教你怎樣追女神。
那一年,日僅僅代表太陽;那一年,菊花還只是一種花;那一年,肥皂只代表用來洗澡;那一年,人們還敢單純的嚮往青青草原;那一年,小鹿還沒有去做頭髮;那一年......那一年甚至還沒有微信,那一年我們用的最多的通訊工具是QQ,那一年,我的女神出現了,於千萬萬人之中,只看她一眼,這世上便再無其他女子。
千方百計以同學的名義加到了她的QQ,隨即設置了特別關注。Q的特別關注大家應該都知道吧,就是隻要她上線、發表心情、發表日至、發表照片等,我都能第一時間收到通知,於是,我,這個世界的第一條舔狗,人稱舔狗鼻祖,就此誕生。
下面忍痛揮淚帶大家一起回顧我當年的舔狗生涯吧。
代碼實例
1、編寫抽象主題角色接口(qq的特別關注功能)
package com.mazhichu.designpatterns.observer;
/**
* <p class="detail">
* 功能: 特別關注主題接口(抽象主題角色)
* </p>
*
* @author Moore
* @ClassName Special attention subject.
* @Version V1.0.
* @date 2019.11.01 15:37:21
*/
public interface SpecialAttentionSubject {
/**
* <p class="detail">
* 功能: 添加觀察者
* </p>
*
* @param followerObserver :
* @author Moore
* @date 2019.11.01 15:37:21
*/
public void addFollower(FollowerObserver followerObserver);
/**
* <p class="detail">
* 功能: 刪除觀察者
* </p>
*
* @param followerObserver :
* @author Moore
* @date 2019.11.01 15:37:21
*/
public void removeFollower(FollowerObserver followerObserver);
/**
* <p class="detail">
* 功能: 通知觀察者
* </p>
*
* @author Moore
* @date 2019.11.01 15:37:21
*/
public void notifyFollowers();
}
2、編寫具體主題角色類(我把女神設置爲特別關注後,qq會將我加到女神的追隨者列表中,女神一有動態,就會通知我)
package com.mazhichu.designpatterns.observer;
import java.util.ArrayList;
import java.util.List;
/**
* <p class="detail">
* 功能:女神的特別關注主題(具體主題角色)
* </p>
*
* @author Moore
* @ClassName Goddess special attention subject.
* @Version V1.0.
* @date 2019.11.01 15:32:46
*/
public class GoddessSpecialAttentionSubject implements SpecialAttentionSubject{
// 包含抽象觀察者的集合
private List<FollowerObserver> followerObservers;
private String content;
public GoddessSpecialAttentionSubject() {
this.followerObservers = new ArrayList<FollowerObserver>();
}
/**
* <p class="detail">
* 功能: 添加觀察者
* </p>
*
* @param followerObserver :
* @author Moore
* @date 2019.11.01 15:37:21
*/
@Override
public void addFollower(FollowerObserver followerObserver) {
followerObservers.add(followerObserver);
}
/**
* <p class="detail">
* 功能: 刪除觀察者
* </p>
*
* @param followerObserver :
* @author Moore
* @date 2019.11.01 15:37:21
*/
@Override
public void removeFollower(FollowerObserver followerObserver) {
followerObservers.remove(followerObserver);
}
/**
* <p class="detail">
* 功能: 通知觀察者
* </p>
*
* @author Moore
* @date 2019.11.01 15:37:21
*/
@Override
public void notifyFollowers() {
followerObservers.forEach(follower -> follower.update(content));
}
/**
* 女神發表動態以後,通知給所有把女神設置成特別關注的人
* @param content
*/
public void sendDynamic(String content){
this.content = content;
notifyFollowers();
}
}
3、追女神接口類(這個類與觀察者模式無關,只爲將我悲慘的故事講的完整一些,讓大家記憶更深刻)
package com.mazhichu.designpatterns.observer;
/**
* <p class="detail">
* 功能:追女神接口(和觀察者模式無關,只爲豐富故事完整性)
* </p>
*
* @author Moore
* @ClassName Woo goddess.
* @Version V1.0.
* @date 2019.11.01 15:58:50
*/
public interface WooGoddess {
public void woo();
}
4、編寫抽象觀察者接口(當收到女神動態時,將女神設爲特別關注的人會做什麼呢)
package com.mazhichu.designpatterns.observer;
/**
* <p class="detail">
* 功能: 關注者的觀察者接口(抽象觀察者角色)
* </p>
*
* @author Moore
* @ClassName Follower observer.
* @Version V1.0.
* @date 2019.11.01 15:18:50
*/
public interface FollowerObserver {
/**
* <p class="detail">
* 功能: 收到通知後,更新
* </p>
*
* @param content :
* @author Moore
* @date 2019.11.01 15:20:16
*/
public void update(String content);
/**
* <p class="detail">
* 功能: 主動添加特別關注
* </p>
*
* @param subject :
* @author Moore
* @date 2019.11.01 15:21:45
*/
public void addSpecialAttention(SpecialAttentionSubject subject);
}
5、編寫具體觀察者角色類(我,舔狗鼻祖上線)
package com.mazhichu.designpatterns.observer;
/**
* <p class="detail">
* 功能: 我這條舔狗(具體觀察者角色)
* </p>
*
* @author Moore
* @ClassName My observer.
* @Version V1.0.
* @date 2019.11.01 16:10:25
*/
public class MyObserver implements FollowerObserver,WooGoddess {
private String content;
/**
* <p class="detail">
* 功能: 收到通知後,更新
* </p>
*
* @param content :
* @author Moore
* @date 2019.11.01 15:20:16
*/
@Override
public void update(String content) {
this.content = content;
woo();
}
/**
* <p class="detail">
* 功能: 主動添加特別關注
* </p>
*
* @param subject :
* @author Moore
* @date 2019.11.01 15:21:45
*/
@Override
public void addSpecialAttention(SpecialAttentionSubject subject) {
subject.addFollower(this);
}
@Override
public void woo() {
System.out.println("我收到女神最新動態:"+content);
if(content.contains("感冒")){
System.out.println("我給女神評論:你要多喝開水,照顧好自己哦!");
}
if(content.contains("生日")){
System.out.println("我給女神評論:生日快樂!永遠17歲!女神給我回復了謝謝併發了一張笑臉,我激動了一夜沒睡!");
}
if(content.contains("離開")){
System.out.println("我給女神留言:無論你走到哪裏,都祝福你一切都好!從此沒有收到女神的信息!");
}
}
}
5、編寫具體觀察者角色(說起來這個就要心痛,當時有太多情敵了,但是隻有這個情敵讓我有危機感)
package com.mazhichu.designpatterns.observer;
/**
* <p class="detail">
* 功能: 情敵(具體觀察者角色)
* </p>
*
* @author Moore
* @ClassName Rival in love observer.
* @Version V1.0.
* @date 2019.11.01 16:11:02
*/
public class RivalInLoveObserver implements FollowerObserver,WooGoddess {
private String content;
/**
* <p class="detail">
* 功能: 收到通知後,更新
* </p>
*
* @param content :
* @author Moore
* @date 2019.11.01 15:20:16
*/
@Override
public void update(String content) {
this.content = content;
woo();
}
/**
* <p class="detail">
* 功能: 主動添加特別關注
* </p>
*
* @param subject :
* @author Moore
* @date 2019.11.01 15:21:45
*/
@Override
public void addSpecialAttention(SpecialAttentionSubject subject) {
subject.addFollower(this);
}
@Override
public void woo() {
System.out.println("情敵收到女神最新動態:"+content);
if(content.contains("感冒")){
System.out.println("情敵給女神送去了感冒藥,並囑咐好好休養,女神很感動!");
}
if(content.contains("生日")){
System.out.println("情敵買了鮮花和蛋糕,還送了禮物,是女神最愛的周杰倫演唱會門票,兩個人一起去看了!");
}
if(content.contains("離開")){
System.out.println("情敵去了女神的城市,三個月後,情敵和女神在一起了!");
}
}
}
6、測試類(在自己的傷口上親自撒鹽,讓我帶你們回顧我當年是怎樣與女神擦肩而過的過程)
package com.mazhichu.designpatterns.observer;
public class ObserverStory {
public static void main(String[] args) {
GoddessSpecialAttentionSubject subject = new GoddessSpecialAttentionSubject();
FollowerObserver myObserver = new MyObserver();
myObserver.addSpecialAttention(subject);
FollowerObserver rivalInLoveObserver = new RivalInLoveObserver();
rivalInLoveObserver.addSpecialAttention(subject);
subject.sendDynamic("天氣轉涼了,有點小感冒");
System.out.println("---------------------\n");
subject.sendDynamic("聽劉德華的17歲,祝自己17歲生日快樂");
System.out.println("---------------------\n");
subject.sendDynamic("畢業季,即將離開這座城市,下一站,杭州!");
}
}
7、男人哭吧哭吧不是罪,看看最終的結局:
以上就是我的血淚史,也是一本教科書的追女孩子祕籍。舔狗舔狗,舔到最後一無所有,通過這次打擊,讓我明白喜歡一個女孩子就要大膽勇敢的去表白去追,男人一定要自信點,主動點,機會要自己去創造,這樣纔有可能成爲人生贏家。
同時,這也是一個典型的觀察者模式,我和情敵是觀察者(Observer),我們的特別關注女神是被觀察者(Subject),女神每次狀態改變(發動態)都會通知我們,我和情敵就會根據女神動態改變我們自身,雖然我輸的徹底,但也算輸的明白了。
觀察者模式的兩種模型
觀察者模式又分爲推模型和拉模型。
-
推模型:顧名思義,就是Subject主動將變更的狀態和數據全部推送給觀察者,而不管觀察者是否需要。
-
拉模型:觀察者持有主題對象的引用,也就是Subject把自身作爲參數傳遞給觀察者,觀察者可以根據自己需要獲取數據,而不用獲取主題的所有狀態和數據。
前面我與女神的故事(其實是我一廂情願)就是推模型,只要女神更新了動態,不管我想不想看,Q都會主動推送給我。那麼拉模型呢?不想傷口一次次被撕開,所以,拉模型,我用另一個例子來簡單說說。很多朋友都下載了騰訊新聞、虎撲、網易、頭條這樣的app,應該都記得首次登錄的時候會讓用戶設置關注的模塊或者內容什麼的,如果你設置了,就會爲你單獨展示該模塊,每次點擊模塊就會獲取相應內容,如果你不想看,就不用點擊推薦模塊。這就是典型的拉模型,按需獲取。下面來看看代碼實例。
拉模型代碼示例
1、還是編寫抽象主題角色
package com.mazhichu.designpatterns.observer;
/**
* <p class="detail">
* 功能: 抽象主題角色
* </p>
*
* @author Moore
* @ClassName Subject.
* @Version V1.0.
* @date 2019.11.04 10:42:57
*/
public interface Subject {
/**
* <p class="detail">
* 功能: 增加觀察者對象
* </p>
*
* @param observer :
* @author Moore
* @date 2019.11.04 10:42:57
*/
public void add(Observer observer);
/**
* <p class="detail">
* 功能: 刪除觀察者對象
* </p>
*
* @param observer :
* @author Moore
* @date 2019.11.04 10:42:57
*/
public void remove(Observer observer);
/**
* <p class="detail">
* 功能: 通知觀察者
* </p>
*
* @author Moore
* @date 2019.11.04 10:42:58
*/
public void notifyObserver();
}
2、編寫具體主題類
package com.mazhichu.designpatterns.observer;
import lombok.Getter;
import java.util.ArrayList;
import java.util.List;
/**
* <p class="detail">
* 功能: 具體主題類
* </p>
*
* @author Moore
* @ClassName Concrete subject.
* @Version V1.0.
* @date 2019.11.04 13:59:11
*/
public class ConcreteSubject implements Subject {
// 包含抽象觀察者的集合
private List<Observer> observers;
@Getter
private String basketball;
@Getter
private String football;
@Getter
private String showbiz;
public ConcreteSubject() {
this.observers = new ArrayList<Observer>();
}
/**
* <p class="detail">
* 功能: 增加觀察者對象
* </p>
*
* @param observer :
* @author Moore
* @date 2019.11.04 10:42:57
*/
@Override
public void add(Observer observer) {
observers.add(observer);
}
/**
* <p class="detail">
* 功能: 刪除觀察者對象
* </p>
*
* @param observer :
* @author Moore
* @date 2019.11.04 10:42:57
*/
@Override
public void remove(Observer observer) {
observers.remove(observer);
}
/**
* <p class="detail">
* 功能: 通知觀察者
* </p>
*
* @author Moore
* @date 2019.11.04 10:42:58
*/
@Override
public void notifyObserver() {
observers.forEach(observer -> observer.update(this));
}
/**
* 發佈最新動態
*
* @param basketball
* @param football
* @param showbiz
*/
public void publishNewestNews(String basketball, String football, String showbiz) {
this.basketball = basketball;
this.football = football;
this.showbiz = showbiz;
System.out.println("發佈最新籃球動態:" + basketball);
System.out.println("發佈最新足球動態:" + football);
System.out.println("發佈最新娛樂圈動態:" + showbiz + "\n");
this.notifyObserver();
}
}
3、編寫抽象觀察者
package com.mazhichu.designpatterns.observer;
/**
* <p class="detail">
* 功能: 抽象觀察者
* </p>
*
* @author Moore
* @ClassName Observer.
* @Version V1.0.
* @date 2019.11.04 14:01:06
*/
public interface Observer {
/**
* 觀察者收到通知後更新自己
*
* @param subject 抽象主題對象
* @author Moore
* @date 2019.11.04 14:01:06
*/
void update(Subject subject);
}
4、編寫具體觀察者角色類
package com.mazhichu.designpatterns.observer;
/**
* <p class="detail">
* 功能: 具體觀察者角色類
* </p>
*
* @author Moore
* @ClassName Concrete observer.
* @Version V1.0.
* @date 2019.11.04 14:02:02
*/
public class ConcreteObserver implements Observer {
/**
* 觀察者名稱
*/
private String name;
// 關注主題分類
private String subjectType;
/**
* 主題對象
*/
private Subject subject;
public ConcreteObserver(String name, String subjectType) {
this.name = name;
this.subjectType = subjectType;
}
/**
* 觀察者響應(將主題對象傳給觀察者,觀察者自己決定獲取哪些信息)
*
* @param subject 抽象主題對象
*/
@Override
public void update(Subject subject) {
this.subject = subject;
if (subjectType.equals("籃球")) {
String basketball = ((ConcreteSubject) subject).getBasketball();
System.out.println(name + "關注的虎撲板塊是:" + subjectType);
System.out.println(name + "收到" + subjectType + "新聞:" + basketball + "\n");
}
if (subjectType.equals("足球")) {
String football = ((ConcreteSubject) subject).getFootball();
System.out.println(name + "關注的虎撲板塊是:" + subjectType);
System.out.println(name + "收到" + subjectType + "新聞:" + football + "\n");
}
if (subjectType.equals("娛樂圈")) {
String showbiz = ((ConcreteSubject) subject).getShowbiz();
System.out.println(name + "關注的虎撲板塊是:" + subjectType);
System.out.println(name + "收到" + subjectType + "新聞:" + showbiz + "\n");
}
}
}
5、測試類,看看觀察者模式的拉模型
package com.mazhichu.designpatterns.observer;
public class Test {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
ConcreteObserver zhangsan = new ConcreteObserver("張三","籃球");
ConcreteObserver lisi = new ConcreteObserver("李四","足球");
ConcreteObserver wangwu = new ConcreteObserver("王五","娛樂圈");
subject.add(zhangsan);
subject.add(lisi);
subject.add(wangwu);
String basketball = "湖人103:96戰勝馬刺";
String football = "梅西成爲6冠王";
String showbiz = "《少年的你》票房破10億";
subject.publishNewestNews(basketball,football,showbiz);
System.out.println("------------------------------------------------\n");
subject.remove(wangwu);
subject.publishNewestNews("騎士不敵獨行俠","巴薩遭遇黑色七分鐘","胡歌新電影即將上映");
}
}
6、查看結果
通過運行結果,我們可以看出拉模型實現了觀察者按需獲取主題的狀態和數據,其實拉模型和推模型最大的區別就是拉模型中觀察者持有主題對象,可以直接操作主體對象。
Java內置的觀察者模式框架Observable類和Observer接口
這個只是想告訴大家有這個東西,但是並不想在這篇文章裏做出詳解,網上關於這個有太多的講解與實例,如果小夥伴們感興趣,可以自己看一下源碼或者看看別人的文章,側重看一下setChanged()方法。
總結
優點
-
觀察者模式是松耦合的,改變主題或觀察者中的一方,另一方不會受到影像。
-
觀察者模式支持廣播通信,被觀察者會向所有註冊過的觀察者對象發出通知。
-
滿足“開閉原則”,新增主題(被觀察者目標)和觀察者都很方便,也不會影響原有角色。
缺點
-
觀察者過多,通知會花費很多時間。
-
如果主題目標和觀察者之間存在循環依賴,可能會循環調用造成系統崩潰。
-
JDK內置的觀察者模式,限制了複用能力。
我不能保證我寫的文章都是正確的,但是我能保證都是我自己花時間用心寫的,所有的代碼示例都是原創,所有的理解都只是我個人理解,不能代表官方權威,所以請各位讀者閱讀時帶着批判的眼光,有選擇性的認同,謝謝!
文章同步公衆號:碼之初,每天推送Java技術文章,期待您的關注!
原創不易,轉載請註明出處,謝謝!