觀察者模式的Java實現及應用

2021-03-02 Java知音

點擊上方「Java知音」,選擇「置頂公眾號」

技術文章第一時間送達!

作者:Turwe

www.jianshu.com/p/1025f644f100

觀察者模式定義

觀察者模式定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知並自動更新。

關鍵字

Observable

即被觀察者,也可以被叫做主題(Subject)是被觀察的對象。通常有註冊方法(register),取消註冊方法(remove)和通知方法(notify)。

Observer

即觀察者,可以接收到主題的更新。當對某個主題感興趣的時候需要註冊自己,在不需要接收更新時進行註銷操作。

例子與應用

舉一個生活中的例子:比如用戶從報社訂閱報紙,報社和用戶之間是一對多依賴,用戶可以在報社訂閱(register)報紙,報社可以把最新的報紙發給用戶(notify),用戶自動收到更新。在用戶不需要的時候還可以取消註冊(remove)。

再比如Android中的EventBus,Rxjava的實現都是基於觀察者模式的思想。再比如回調函數:Android中對Button的點擊監聽等等。

觀察者模式可以用來解耦

自己用代碼實現一個觀察者模式

現在我們用代碼來實現上面訂閱報紙的例子:

NewProvider作為對於報社的抽象,每隔兩秒鐘向用戶發送報紙;User作為用戶的抽象,可以收到報紙。NewsModel作為對報紙本身的抽象。

/**
 * 被觀察者接口定義
 */
public interface MyObserverable {

    void register(MyObserver myObserver);

    void remove(MyObserver myObserver);

    void send(NewsModel model);

}

/**
 * 觀察者接口定義
 */
public interface MyObserver {

    void receive(NewsModel model);

}

/**
 * 對於報社的抽象,實現了被觀察者接口,每隔2s發送一次報紙
 */
public class NewsProvider implements MyObserverable {
    private static final long DELAY = 2 * 1000;
    private List<MyObserver> mObservers; //我們用一個List來維護所有的觀察者對象

    public NewsProvider() {
        mObservers = new ArrayList<>();
        generateNews();
    }

    /**
     * 模擬產生新聞,每個2s發送一次
     */
    private void generateNews() {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            int titleCount = 1;
            int contentCount = 1;

            @Override
            public void run() {
                send(new NewsModel("title:" + titleCount++, "content:" + contentCount++));
            }
        }, DELAY, 1000);
    }

    @Override
    public void register(MyObserver myObserver) {
        if (myObserver == null)
            return;
        synchronized (this) {
            if (!mObservers.contains(myObserver))
                mObservers.add(myObserver);
        }
    }

    @Override
    public synchronized void remove(MyObserver myObserver) {
        mObservers.remove(myObserver);
    }

    @Override
    public void send(NewsModel model) {
        for (MyObserver observer : mObservers) {
            observer.receiveNews(model);
        }
    }
}

/**
 * 對於用戶的抽象
 */
public class User implements MyObserver {
    private String mName;

    public User(String name) {
        mName = name;
    }

    @Override
    public void receive(NewsModel model) {
        System.out.println(mName + " receive news:" + model.getTitle() + "  " + model.getContent());
    }
}

/**
 * 測試類
 */
public class Test {
    public static void main(String[] args) {

        NewsProvider provider = new NewsProvider();
        User user;
        for (int i = 0; i < 10; i++) {
            user = new User("user:"+i);
            provider.register(user);
        }

    }
}

運行結果如下:

這樣我們就自己動手完成了一個簡單的觀察者模式。

其實在JDK的util包內Java為我們提供了一套觀察者模式的實現,在使用的時候我們只需要繼承Observable和Observer類即可,其實觀察者模式十分簡單,推薦閱讀JDK的實現代碼真心沒有幾行。此外在JDK的實現中還增加了一個布爾類型的changed域,通過設置這個變量來確定是否通知觀察者。

下面我們用JDK的類來實現一遍我們的觀察者模式:

public class NewsProvider extends Observable {
    private static final long DELAY = 2 * 1000;

    public NewsProvider() {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
           private int titleCount = 1;
           private int contentCount = 1;
            @Override
            public void run() {
                setChanged(); //調用setChagned方法,將changed域設置為true,這樣才能通知到觀察者們
                notifyObservers(new NewsModel("title:" + titleCount++, "content:" + contentCount++));
            }
        }, DELAY, 1000);
    }
}

public class User implements Observer {
    private String mName;

    public User(String name) {
        mName = name;
    }

    @Override
    public void update(Observable observable, Object data) {
        NewsModel model = (NewsModel) data;
        System.out.println(mName + " receive news:" + model.getTitle() + "  " + model.getContent());
    }
}

非常簡單有木有

回調函數與觀察者模式
關於回調函數的定義在知乎上看到過一個很贊的解釋:

你到一個商店買東西,剛好你要的東西沒有貨,於是你在店員那裡留下了你的電話,過了幾天店裡有貨了,店員就打了你的電話,然後你接到電話後就到店裡去取了貨。在這個例子裡,你的電話號碼就叫回調函數,你把電話留給店員就叫登記回調函數,店裡後來有貨了叫做觸發了回調關聯的事件,店員給你打電話叫做調用回調函數,你到店裡去取貨叫做響應回調事件。回答完畢。

在Android中我們有一個常用的回調:對與View點擊事件的監聽。現在我們就來分析一下對於View的監聽。

通常在我們使用的時候是這樣的:

        xxxView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // do something
            }
        });

這樣我們就註冊好了一個回調函數。

我們可以在View的源碼裡發現這個接口:

    /**
     * Interface definition for a callback to be invoked when a view is clicked.
     */
    public interface OnClickListener {
        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        void onClick(View v);
    }

當你setClickListener的時候在View的源碼中可以看到對本地OnClickListener的初始化

    /**
     * Register a callback to be invoked when this view is clicked. If this view is not
     * clickable, it becomes clickable.
     *
     * @param l The callback that will run
     *
     * @see #setClickable(boolean)
     */
    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

當你的點擊到一個View後Android系統經過一系列的調用最後到了View的performClick方法中:

/**
     * Call this view's OnClickListener, if it is defined.  Performs all normal
     * actions associated with clicking: reporting accessibility event, playing
     * a sound, etc.
     *
     * @return True there was an assigned OnClickListener that was called, false
     *         otherwise is returned.
     */
    public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

就在這裡,觸發了你的onClick方法,然後執行方法體。

這裡我們的被觀察者就是View,他的註冊方法(register)就是setOnClickListener(),通知方法就是performClick;而OnClickListener就是觀察者。只不過這裡的只能註冊一個觀察對象而已。

參考

https://www.zhihu.com/question/19801131/answer/13005983#showWechatShareTip

END

Java面試題專欄

我知道你 「在看

相關焦點

  • 觀察者模式的 Java 實現及應用
    觀察者模式定義觀察者模式定義了對象之間的一對多依賴
  • 如何應用觀察者設計模式重構系統中日誌處理功能實現的程序代碼
    軟體項目實訓及課程設計指導——如何應用觀察者設計模式重構系統中的日誌處理功能實現的程序代碼1、GOF設計模式中的觀察者設計模式(1)什麼是觀察者設計模式GOF設計模式中的觀察者設計模式定義了一種解耦「一對多」的依賴關係的編程模式
  • 觀察者模式
    觀察者模式屬於行為型模式。介紹意圖:定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。主要解決:一個對象狀態改變給其他對象通知的問題,而且要考慮到易用和低耦合,保證高度的協作。何時使用:一個對象(目標對象)的狀態發生改變,所有的依賴對象(觀察者對象)都將得到通知,進行廣播通知。
  • Gof23種設計模式(20)——觀察者模式
    具體觀察者角色實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與主題的狀態相協調。如果需要,具體觀察者角色可以保存一個指向具體主題對象的引用。具體觀察者角色通常用一個具體子類實現。2 應用場景舉例比如公司的通訊錄是大家都要使用的,同時也是經常變化的。
  • 觀察者模式解析
    ❞設計模式的學習,我的建議是,首先弄懂它的含義,和前人已經總結沉澱下來的設計原則。將這些概念記在心裡,當工作中碰見對應的需求開發工作時,按照原則去認真思考、設計實現,最終符合要求的結果往往就已經是一些設計模式的具體實現了。
  • 觀察者模式--設計模式(一)
    (發布訂閱)模式,因此在弄清開始SpringBoot的事件監聽機制的源碼分析前,先來學習下觀察者模式,嘿嘿。24,自己實現觀察者模式現在我們就用代碼來實現前面提到的體育愛好者訂閱體育賽事信息的例子。當觀察者取消訂閱某個之前訂閱的體育賽事,此時就不會推送給這個觀察者了。    因為接下來要分析SpringBoot的事件監聽機制,而SpringBoot的事件監聽機制就是基於觀察者(發布訂閱)模式實現的,是觀察者模式的具體應用案例。因此,在學習前是很有必要學習下觀察者模式的。這樣才能做到理論(設計模式)聯繫實踐(springboot的事件監聽機制)。5
  • 五分鐘學會觀察者模式
    介紹觀察者模式:多個觀察者同時監聽一個主題對象,當主題對象發生改變時,它的所有觀察者都會收到通知。例如微信公眾號,當作者發文時,所有的訂閱者都會收到。這樣觀察者模式就能實現廣播,同時符合開閉原則,增加新的觀察者不用改原有的代碼。觀察者模式的UML圖如下
  • java 23種設計模式及其在spring,tomcat,jdk中的應用
    設計模式是前人(一般都是大師)對程序設計經驗的總結,學習並應用設計模式可以使我們開發的項目更加規範、便於擴展和維護,這大概是為什麼設計模式基本是面試必問的原因吧!本文主要內容有四部分:設計原則、設計模式分類、23種設計模式、設計模式應用。設計模式雖多,但重要的、主流開源框架應用較多的往往就那幾個。
  • 使用Java API在Jedis中實現DAO設計模式
    在本文中,我們將學習Jedis Java客戶端中的DAO設計模式和實現。DAO模式被實現為客戶端應用程式和資料庫之間的一層。客戶端應用程式不必依賴基礎資料庫交互API(低級)。Redis資料庫中存儲的數據被建模為Domain對象(POJO類),它將僅具有getter和setter方法,客戶端應用程式僅知道域對象和高級API。
  • 從Java類庫看設計模式
    java.util.Observerable類對應於Subject,而java.util.Observer就是觀察者了。JDK中並沒有把這兩個部分都設計為接口,而是讓類java.util.Observerable提供了部分的實現,簡化了許多編程的工作。當然,這也減少了一定的靈活性。
  • 通俗易懂的告訴你「策略模式」在java多線程中的應用
    花10分鐘認真的閱讀一篇文章有時或許比敲60分鐘代碼還有效我們都知道java啟動多線程有兩種方式,一種是繼承Thread類,一種是實現Runnable接口,但是很多小夥伴可能不知道實現Runnable接口這種方式中運用了
  • 設計模式之觀察者模式(一)
    前面兩篇已經帶大家走進了設計模式的世界,了解了策略模式,還有基本的OO基礎和OO原則,不知道你是否能讀懂以及了解呢。接下來,我們就要進入第二個模式的學習了,觀察者模式,讓我們來一窺究竟吧。觀察者模式是JDK中使用最多的模式之一,可以幫你的對象知悉情況,不會錯過該對象感興趣的事。對象甚至在運行時可決定是否要繼續被通知。並且後續還會一併介紹一對多關係,以及鬆耦合。
  • C++設計模式:Observer(觀察者模式)
    假設這樣的需求:我們需要實現一個GUI程序,這個程序用來展示文件分割的進度.但是文件的分割進度可能會有不同的表示,比如我們經常看到的進度處理有:進度條展現百分比展現##號展現我們需要未來能支持到不同的通知模式.代碼架構:主程序(此代碼為了模仿GUI程序中的主窗口):
  • 大神詳解,這麼詳細的Java設計模式不收藏可惜了
    為什麼再看一遍設計模式,主要有幾個原因:***,很多優秀的源碼基本都使用了設計模式,明確設計模式能夠更好的看源碼。第二,很多中間件設計理念也是基於設計模式的,還有其他的語言,都有自己的設計優秀實踐。對於我來說,設計模式始於java,不止於java。第三,有了這種規範,可以更好的和他人溝通,言簡意賅。
  • 觀察者模式和訂閱發布模式是一樣的嗎?
    看到一篇介紹關於觀察者模式和訂閱發布模式的區別的文章,看完後依然認為它們在概念和思想上是統一的,只是根據實現方式和使用場景的不同,叫法不一樣,不過既然有區別,就來探究一番,加深理解。訂閱發布模式該模式理解起來和觀察者模式一樣,也是定義一對多的依賴關係,對象狀態改變後,通知給所有關心這個狀態的訂閱者。
  • Java設計模式:三大類工廠方法模式
    在簡單工廠模式中,一個工廠類處於對產品類實例化調用的中心位置上,它決定那一個產品類應當被實例化, 如同一個交通警察站在來往的車輛流中,決定放行那一個方向的車輛向那一個方向流動一樣。  先來看看它的組成:  1) 工廠類角色:這是本模式的核心,含有一定的商業邏輯和判斷邏輯。在java中它往往由一個具體類實現。
  • 常用的Java設計模式
    觀察者模式的優點和缺點觀察者模式的優點:觀察者模式的缺點:觀察者模式的實現實現觀察者模式時要注意具體目標對象和具體觀察者對象之間不能直接調用,否則將使兩者之間緊密耦合起來,這違反了面向對象的設計原則。1、觀察者模式的結構觀察者模式的主要角色如下:抽象主題(Subject)角色:也叫抽象目標類,它提供了一個用於保存觀察者對象的聚集類和增加、刪除觀察者對象的方法,以及通知所有觀察者的抽象方法。
  • Java門面模式(或外觀模式)
    門面模式(或外觀模式)隱藏系統的複雜性,並為客戶端提供一個客戶端可以訪問系統的接口。 這種類型的設計模式屬於結構模式,因為此模式為現有系統添加了一個接口以隱藏其複雜性。門面模式涉及一個類,它提供客戶端所需的簡化方法和委託調用現有系統類的方法。實現實例在這個實例中將創建一個Shape接口並實現Shape接口的具體類。在下一步中,將定義一個Facade類ShapeMaker,請在下一步中參考其代碼。
  • Java策略模式
    在策略模式中,可以在運行時更改類行為或其算法。 這種類型的設計模式屬於行為模式。在策略模式中,創建表示各種策略對象和其行為根據其策略對象而變化的上下文對象。 策略對象更改上下文對象的執行算法。實現實例在這個示例中,將創建一個 Strategy 接口,定義實現策略接口的操作和具體策略類。 上下文類- Context 是使用策略的類。StrategyPatternDemo是一個演示類,將使用上下文- Context 和策略對象來演示上下文行為基於其部署或使用的策略的變化。
  • 詳解設計模式在 Spring 中的應用
    value> </constructor-arg> </bean> </beans>第二種:工廠方法(Factory Method)通常由應用程式直接使用new創建新的對象,為了將對象的創建和使用相分離,採用工廠模式,即應用程式將對象的創建及初始化職責交給工廠對象