觀察者模式 (Observer)

1.定義

定義對象間的一種一對多的依賴關系咳燕。當一個對象的狀態(tài)發(fā)生改變時咐刨,所有依賴于它的對象都得到通知并被自動更新。

2.類圖

image
  • Subject

目標對象愁憔。具備功能點:1.管理多個觀察者唧领。2.提供對觀察者的注冊及退訂藻雌。3.當狀態(tài)發(fā)生改變時,對訂閱有效的觀察者進行通知疹吃。

  • Observer

觀察者接口蹦疑。定義目標通知后更新操作

  • ConcreteSubject

具體的主題,用來維護自身的狀態(tài)的變化

  • ConcreteObserver

觀察者具體實現(xiàn)對象萨驶,用來接受目標的通知歉摧,并進行后續(xù)的操作。

3.深入理解

  • 1.目標和觀察者的關系按定義區(qū)分:一對多。
  • 2.單向依賴關系叁温。只有觀察者依賴與目標再悼,目標不會依賴與觀察者。

建議:

  • 目標接口定義膝但,建議在類名稱后面加上Subject
  • 觀察者接口定義冲九,建議在類名稱后面加上Observer
  • 觀察者接口方法,建議命名為update

4.代碼

  • Subject
/**
 * 目標對象跟束,它知道觀察它的觀察者莺奸,并提供注冊和刪除觀察者的接口
 */
public class Subject {

    /**
     * 用于保存觀察者對象
     */
    private List<Observer> observers = new ArrayList<>();

    /**
     * 注冊觀察者
     *
     * @param observer
     */
    public void attach(Observer observer) {
        observers.add(observer);
    }

    /**
     * 刪除觀察者對象
     *
     * @param observer
     */
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    /**
     * 通知所有注冊的觀察者對象
     */
    protected void notifyObservers() {
        observers.stream().forEach(observer -> observer.update(this));
    }
}
  • Observer
/**
 * 觀察者接口,定義一個更新的接口給那些在目標發(fā)生改變的時候被通知的后的操作
 */
public interface Observer {

    /**
     * 更新接口
     *
     * @param subject
     */
    void update(Subject subject);
}

  • ConcreteSubject
/**
 * 具體的目標對象冀宴,負責把有關狀態(tài)存入到相應的觀察者
 * 并在自己狀態(tài)發(fā)生改變時灭贷,通知各個觀察者
 */
public class ConcreteSubject extends Subject {

    /**
     * 示意目標對象
     */
    private String subjectState;

    public String getSubjectState() {
        return subjectState;
    }

    public void setSubjectState(String subjectState) {
        this.subjectState = subjectState;
        System.out.println("目標對象已更新自身狀態(tài)為:" + this.subjectState);
        this.notifyObservers();
    }
}
  • ConcreteObserver
/**
 * 具體的觀察者對象,實現(xiàn)更新方法略贮,使得自身的狀態(tài)和目標狀態(tài)保持一致
 */
public class ConcreteObserver implements Observer {

    /**
     * 示意甚疟,觀察者對象狀態(tài)
     */
    private String observerState = "呀,我是觀察者初始狀態(tài)逃延!";

    @Override
    public void update(Subject subject) {
        System.out.println("觀察者之前的狀態(tài)為:" + this.observerState);
        observerState = ((ConcreteSubject) subject).getSubjectState();
        System.out.println("觀察者接收到更新的對象狀態(tài):" + observerState);
    }
}

  • ObserverClient
/**
 * 調用示例
 */
public class ObserverClient {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        Observer observer=new ConcreteObserver();
        subject.attach(observer);
        subject.setSubjectState("121");
    }
}

5. 觀察者兩種通知模式(推模型和拉模型)

5.1 推模型

目標對象主動向觀察者推送目標的詳細信息览妖,不管觀察者是否需要,推送的信息通常是目標對象的全部或者部分數(shù)據(jù)揽祥,相當于在廣播讽膏。

5.2 拉模型

目標對象在通知觀察者的時候,只傳遞少量信息盔然。如果觀察者需要更具體的信息桅打,由觀察者主動到目標對象中獲取是嗜,相當于是觀察者從目標對象中拉數(shù)據(jù)愈案。

拉模型的處理方式,會把目標對象自身通過update方法傳遞給觀察者鹅搪,這樣在觀察者需要獲取數(shù)據(jù)的時候站绪,就可以通過這個引用來獲取了。

上面的代碼就是典型的拉模型丽柿。在具體的主題里恢准,通過update方法把自身傳遞給具體的觀察者。

5.3 如何將上面的拉模型轉變?yōu)橥颇P?/h4>

處理步驟

  • 1.主題類的批量通知方法甫题,增加需要通知的參數(shù)
  • 2.具體觀察者的update方法接受具體通知信息
  • PushSubject
public class PushSubject {
    private List<PushObserver> observers = new ArrayList<>();

    /**
     * 訂閱主題
     *
     * @param observer
     */
    public void attach(PushObserver observer) {
        observers.add(observer);
    }

    /**
     * 取消訂閱
     *
     * @param observer
     */
    public void detach(PushObserver observer) {
        observers.remove(observer);
    }

    /**
     * 拉模型通知所有的注冊者
     */
    protected void notifyObservers(String content) {
        observers.stream().forEach(observer -> {observer.update(content);});
    }
}

  • ConcretePushSubject
public class ConcretePushSubject extends PushSubject {
    /**
     * 示意目標對象
     */
    private String subjectState;

    public String getSubjectState() {
        return subjectState;
    }

    public void setSubjectState(String subjectState) {
        this.subjectState = subjectState;
        System.out.println("目標對象已更新自身狀態(tài)為:" + this.subjectState);
        this.notifyObservers(subjectState);
    }
}

  • PushObserver
public interface PushObserver {
    /**
     * 更新接口
     * 傳入更新的內容
     * @param content
     */
    void update(String content);
}

  • ConcretePushObserver
public class ConcretePushObserver implements PushObserver {
    @Override
    public void update(String content) {
        System.out.print("主題推送的信息:" + content);
    }
}
  • Main
public static void main(String[] args) {
    ConcretePushSubject pushSubject=new ConcretePushSubject();
    ConcretePushObserver pushObserver=new ConcretePushObserver();
    pushSubject.attach(pushObserver);
    pushSubject.setSubjectState("1212");
}

5.4 如何選擇拉模型與推模型

推模型是假定主題對象知道觀察者需要的數(shù)據(jù)馁筐。退模型的一個缺點就是:可能會使得觀察者對象難以復用,因為觀察者對象的update方法是按需而定義的坠非。ps:為了避免這樣的問題敏沉,主題對象通常定義一個公用的話題。類比:微信公眾號,公眾號更新內容盟迟,直接推送給訂閱的用戶秋泳。

拉模型是主題對象無法得知觀察者具體需要什么數(shù)據(jù),干脆把自身對象傳遞給觀察者攒菠,讓觀察者按需提取數(shù)據(jù)迫皱。

6.Java中的觀察者

Java語言已經內置好了觀察者的實現(xiàn)。

java.util包中定義了Observable辖众,代表具備可以被觀察的能力卓起。也就是我們之前定義的Subject類。目標接口

java.util包中也定義了一個接口Observer凹炸,代表觀察者對象接口既绩。里面定義了update方法

6.1好處

  • 1.無須額外定義觀察者和目標對象的接口,JDK已經內置还惠。
  • 2.具體的目標實現(xiàn)里面無須再維護觀察者的注冊信息饲握,這個在Observable類中已經定義好
  • 3.觸發(fā)方式要先調用setChanged()方法
  • 4.具體的觀察者的實現(xiàn)里,update方法能夠同時支持推模型和拉模型蚕键。

6.2代碼

  • 1.定義主題類
import java.util.Observable;

public class NewPaper extends Observable {
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
        this.setChanged();
        //this.notifyObservers();//拉模型
        this.notifyObservers(content);//推模型
    }
}
  • 2.定義觀察者類
import java.util.Observable;
import java.util.Observer;

public class Reader implements Observer {

    //讀者名稱
    private String name;

    public Reader(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void update(Observable o, Object arg) {
        System.out.println(name + "收到報紙了救欧,目標推送過來的內容是:" + arg);
        //System.out.println(name + "收到報紙了,主動獲取到目標更新的內容:" + ((NewPaper) o).getContent());
    }
}
  • 3.客戶端調用
public class Program {
    public static void main(String[] args) {
        NewPaper paper = new NewPaper();
        Reader zhangsan = new Reader("zhangsan");
        Reader lisi = new Reader("lisi");
        paper.addObserver(zhangsan);
        paper.addObserver(lisi);
        paper.setContent("iphone 8發(fā)布了B喙狻笆怠!");
    }
}

7.觀察者優(yōu)缺點

7.1 優(yōu)點

  • 1.觀察者模式實現(xiàn)了觀察者與目標之間的抽象耦合
  • 2.觀察者模式實現(xiàn)了動態(tài)聯(lián)動
  • 3.觀察者模式實現(xiàn)了廣播通信。

7.2 缺點

  • 1.可能會引起無謂的操作

因為不管觀察者是否需要誊爹,都需要調用update方法蹬刷。

8.觀察者的本質

觀察者的本質是:觸發(fā)聯(lián)動

當修改目標對象的狀態(tài)時候,就會觸發(fā)相應的通知频丘,然后會循環(huán)調用所有注冊的觀察者對象的相應方法办成。相當于聯(lián)動調用這些觀察者的方法

這個聯(lián)動還是動態(tài)的,可以通過注冊和取消注冊來控制觀察者搂漠。同事目標對象和觀察者對象的解耦迂卢,又保證了無論觀察者發(fā)生怎樣的變化,目標對象總是能夠正確的聯(lián)動過來桐汤。

9.何時選用觀察者

  • 1.當一個抽象模型有兩個方面而克,一個方面依賴于另一個方面的狀態(tài)改變。

  • 2.在更改一個對象時候怔毛,需要同時連帶改變其他的對象员萍,而且不知道究竟有多少對象需要被連帶改變。

  • 3.當一個對象必須通知其他的對象拣度,但是又希望這個對象和其他被它通知的對象是松散耦合的碎绎。

10.與其他設計模式的區(qū)別

10.1 觀察者模式與狀態(tài)模式

兩者是有相似之處蜂莉。但也有所不同,觀察者重心是觸發(fā)聯(lián)動混卵。但是到底決定哪些觀察者進行聯(lián)動映穗,可以配合狀態(tài)模式。

  • 觀察者模式

當目標狀態(tài)發(fā)生改變時幕随,觸發(fā)并通知觀察者蚁滋,讓觀察者去執(zhí)行相應的操作。

  • 狀態(tài)模式

根據(jù)不同的狀態(tài)赘淮,選擇不同的實現(xiàn)辕录。這個實現(xiàn)類的主要功能就是針對狀態(tài)相應的操作。

10.2 觀察者模式與中介者模式

兩個模式可以一起使用梢卸。比如觀察者模式里面的主題與觀察者交互比較復雜走诞,那么就可以一起使用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末蛤高,一起剝皮案震驚了整個濱河市蚣旱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌戴陡,老刑警劉巖塞绿,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異恤批,居然都是意外死亡异吻,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進店門喜庞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诀浪,“玉大人,你說我怎么就攤上這事延都±字恚” “怎么了?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵窄潭,是天一觀的道長春宣。 經常有香客問我,道長嫉你,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任躏惋,我火速辦了婚禮幽污,結果婚禮上,老公的妹妹穿的比我還像新娘簿姨。我一直安慰自己距误,他們只是感情好簸搞,可當我...
    茶點故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著准潭,像睡著了一般趁俊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上刑然,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天寺擂,我揣著相機與錄音,去河邊找鬼泼掠。 笑死怔软,一個胖子當著我的面吹牛,可吹牛的內容都是我干的择镇。 我是一名探鬼主播挡逼,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼腻豌!你這毒婦竟也來了家坎?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤吝梅,失蹤者是張志新(化名)和其女友劉穎乘盖,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體憔涉,經...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡订框,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了兜叨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片穿扳。...
    茶點故事閱讀 40,769評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖国旷,靈堂內的尸體忽然破棺而出矛物,到底是詐尸還是另有隱情,我是刑警寧澤跪但,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布履羞,位于F島的核電站,受9級特大地震影響屡久,放射性物質發(fā)生泄漏忆首。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一被环、第九天 我趴在偏房一處隱蔽的房頂上張望糙及。 院中可真熱鬧,春花似錦筛欢、人聲如沸浸锨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柱搜。三九已至迟郎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間聪蘸,已是汗流浹背宪肖。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留宇姚,地道東北人匈庭。 一個月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像浑劳,于是被迫代替她去往敵國和親阱持。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內容