設(shè)計模式筆記五觀察者模式

每日一文

已不先定,牧人不正,事用不巧,是謂“忘情失道”;己審先定以牧入,策而無形容,莫見其門,是謂“天神”。

觀察者模式

觀察者模式(有時又被稱為發(fā)布-訂閱模式、模型-視圖模式、源-收聽者模式或從屬者模式)是軟件設(shè)計模式的一種。在此種模式中,一個目標(biāo)物件管理所有相依于它的觀察者物件,并且在它本身的狀態(tài)改變時主動發(fā)出通知。這通常透過呼叫各觀察者所提供的方法來實(shí)現(xiàn)。此種模式通常被用來實(shí)作事件處理系統(tǒng)。

類圖


1. 訂閱者模式

  • 觀察者實(shí)現(xiàn)Observer接口
package com.example.patternproxy.observable;

import android.util.Log;

import java.util.Observable;
import java.util.Observer;

/**
 * Created on 2017/3/14.
 * Desc:讀者,訂閱者
 * Author:Eric.w
 */

public class Reader implements Observer {

    private String name;

    public String getName() {
        return name;
    }

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

    }

    public void subcreble(String writerName) {
        WriterManager.getInstance().getWriter(writerName).addObserver(this);
    }

    public void unSubcreble(String writerName) {
        WriterManager.getInstance().getWriter(writerName).deleteObserver(this);
    }

    /**
     * 被觀察者變化時觸發(fā)的方法(notifyObservers())
     *
     * @param o
     * @param arg
     */
    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof Writer) {
            Log.e("pattern", ((Writer) o).getName() + "出新書了,《" + ((Writer) o).getBook() + "》!");
        }
    }
}
  • 被觀察者實(shí)現(xiàn)Observable接口
package com.example.patternproxy.observable;

import android.util.Log;

import java.util.Observable;

/**
 * Created on 2017/3/14.
 * Desc:作者,可被訂閱
 * Author:Eric.w
 */

public class Writer extends Observable {

    private String name;

    private String book;

    public String getBook() {
        return book;
    }

    public String getName() {
        return name;
    }

    public Writer(String name) {
        super();
        this.name = name;
        WriterManager.getInstance().putWriter(this);
    }

    public void addNewBook(String bookname) {
        Log.e("pattern", "public new book :" + bookname);
        this.book = bookname;
        setChanged();
        notifyObservers();
    }
}

  • 為了更好的管理被訂閱者:增加WriterManager,對數(shù)據(jù)是一個很好的分離
package com.example.patternproxy.observable;

import java.util.HashMap;

/**
 * Created on 2017/3/14.
 * Desc:作者管理類
 * Author:Eric.w
 */

public class WriterManager {

    public HashMap<String, Writer> writerMap = new HashMap<>();

    public void putWriter(Writer writer) {
        writerMap.put(writer.getName(), writer);
    }

    public Writer getWriter(String writerName) {
        return writerMap.get(writerName);
    }

    /**
     * 靜態(tài)內(nèi)部類,的靜態(tài)成員變量只在類加載的時候初始化,直郵調(diào)用了getInstance()
     * 才會去加載類創(chuàng)建類的實(shí)例
     */
    public WriterManager() {
    }

    private static class WriterManagerInstance {
        private static WriterManager instance = new WriterManager();
    }

    public static WriterManager getInstance() {
        return WriterManagerInstance.instance;
    }
}

2. 事件驅(qū)動模式

首先事件驅(qū)動模型與觀察者模式勉強(qiáng)的對應(yīng)關(guān)系可以看成是,被觀察者相當(dāng)于事件源,觀察者相當(dāng)于監(jiān)聽器,事件源會產(chǎn)生事件,監(jiān)聽器監(jiān)聽事件。所以這其中就攙和到四個類,事件源,事件,監(jiān)聽器以及具體的監(jiān)聽器。

import java.util.HashSet;
import java.util.Set;

//作者類
public class Writer{
    
    private String name;//作者的名稱
    
    private String lastNovel;//記錄作者最新發(fā)布的小說
    
    private Set<WriterListener> writerListenerList = new HashSet<WriterListener>();//作者類要包含一個自己監(jiān)聽器的列表

    public Writer(String name) {
        super();
        this.name = name;
        WriterManager.getInstance().add(this);
    }

    //作者發(fā)布新小說了,要通知所有關(guān)注自己的讀者
    public void addNovel(String novel) {
        System.out.println(name + "發(fā)布了新書《" + novel + "》!");
        lastNovel = novel;
        fireEvent();
    }
    //觸發(fā)發(fā)布新書的事件,通知所有監(jiān)聽這件事的監(jiān)聽器
    private void fireEvent(){
        WriterEvent writerEvent = new WriterEvent(this);
        for (WriterListener writerListener : writerListenerList) {
            writerListener.addNovel(writerEvent);
        }
    }
    //提供給外部注冊成為自己的監(jiān)聽器的方法
    public void registerListener(WriterListener writerListener){
        writerListenerList.add(writerListener);
    }
    //提供給外部注銷的方法
    public void unregisterListener(WriterListener writerListener){
        writerListenerList.remove(writerListener);
    }
    
    public String getLastNovel() {
        return lastNovel;
    }

    public String getName() {
        return name;
    }

}
import java.util.EventListener;

public interface WriterListener extends EventListener{

    void addNovel(WriterEvent writerEvent);
    
}
public class Reader implements WriterListener{

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

    public String getName() {
        return name;
    }
    
    //讀者可以關(guān)注某一位作者,關(guān)注則代表把自己加到作者的監(jiān)聽器列表里
    public void subscribe(String writerName){
        WriterManager.getInstance().getWriter(writerName).registerListener(this);
    }
    
    //讀者可以取消關(guān)注某一位作者,取消關(guān)注則代表把自己從作者的監(jiān)聽器列表里注銷
    public void unsubscribe(String writerName){
        WriterManager.getInstance().getWriter(writerName).unregisterListener(this);
    }
    
    public void addNovel(WriterEvent writerEvent) {
        Writer writer = writerEvent.getWriter();
        System.out.println(name+"知道" + writer.getName() + "發(fā)布了新書《" + writer.getLastNovel() + "》,非要去看!");
    }

}

首先本來是實(shí)現(xiàn)Observer接口,現(xiàn)在要實(shí)現(xiàn)WriterListener接口,響應(yīng)的update方法就改為我們定義的addNovel方法,當(dāng)中的響應(yīng)基本沒變。另外就是關(guān)注和取消關(guān)注的方法中,原來是給作者類添加觀察者和刪除觀察者,現(xiàn)在是注冊監(jiān)聽器和注銷監(jiān)聽器,幾乎是沒什么變化的。
我們徹底將剛才的觀察者模式改成了事件驅(qū)動,現(xiàn)在我們使用事件驅(qū)動的類再運(yùn)行一下客戶端,其中客戶端代碼和WriterManager類的代碼是完全不需要改動的,直接運(yùn)行客戶端即可。我們會發(fā)現(xiàn)得到的結(jié)果與觀察者模式一模一樣。
走到這里我們發(fā)現(xiàn)二者可以達(dá)到的效果一模一樣,那么兩者是不是一樣呢?
答案當(dāng)然是否定的,首先我們從實(shí)現(xiàn)方式上就能看出,事件驅(qū)動可以解決觀察者模式的問題,但反過來則不一定,另外二者所表達(dá)的業(yè)務(wù)場景也不一樣,比如上述例子,使用觀察者模式更貼近業(yè)務(wù)場景的描述,而使用事件驅(qū)動,從業(yè)務(wù)上講,則有點(diǎn)勉強(qiáng)。
二者除了業(yè)務(wù)場景的區(qū)別以外,在功能上主要有以下區(qū)別。

  1. 觀察者模式中觀察者的響應(yīng)理論上講針對特定的被觀察者是唯一的(說理論上唯一的原因是,如果你愿意,你完全可以在update方法里添加一系列的elseif去產(chǎn)生不同的響應(yīng),但LZ早就說過,你應(yīng)該忘掉elseif),而事件驅(qū)動則不是,因為我們可以定義自己感興趣的事情,比如剛才,我們可以監(jiān)聽作者發(fā)布新書,我們還可以在監(jiān)聽器接口中定義其它的行為。再比如tomcat中,我們可以監(jiān)聽servletcontext的init動作,也可以監(jiān)聽它的destroy動作。
  1. 雖然事件驅(qū)動模型更加靈活,但也是付出了系統(tǒng)的復(fù)雜性作為代價的,因為我們要為每一個事件源定制一個監(jiān)聽器以及事件,這會增加系統(tǒng)的負(fù)擔(dān),各位看看tomcat中有多少個監(jiān)聽器和事件類就知道了。
  2. 另外觀察者模式要求被觀察者繼承Observable類,這就意味著如果被觀察者原來有父類的話,就需要自己實(shí)現(xiàn)被觀察者的功能,當(dāng)然,這一尷尬事情,我們可以使用適配器模式彌補(bǔ),但也不可避免的造成了觀察者模式的局限性。事件驅(qū)動中事件源則不需要,因為事件源所維護(hù)的監(jiān)聽器列表是給自己定制的,所以無法去制作一個通用的父類去完成這個工作。
  3. 被觀察者傳送給觀察者的信息是模糊的,比如update中第二個參數(shù),類型是Object,這需要觀察者和被觀察者之間有約定才可以使用這個參數(shù)。而在事件驅(qū)動模型中,這些信息是被封裝在Event當(dāng)中的,可以更清楚的告訴監(jiān)聽器,每個信息都是代表的什么。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,569評論 19 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,062評論 25 709
  • 1 場景問題# 1.1 訂閱報紙的過程## 來考慮實(shí)際生活中訂閱報紙的過程,這里簡單總結(jié)了一下,訂閱報紙的基本流程...
    七寸知架構(gòu)閱讀 4,810評論 5 57
  • 今天和偉光聊天,他的一句話點(diǎn)醒了我“你到底能留下什么?” 大眾所追求的“名”和“利”,其實(shí)沒給后人留下任何東西。 ...
    思考改變?nèi)松?/span>閱讀 261評論 0 0
  • 換個地方,做了一段時間,從開始的誠惶誠恐到現(xiàn)在的坦然面對,努力吧!希望自己可以有個好的開始!
    墨一1126閱讀 184評論 0 0

友情鏈接更多精彩內(nèi)容