Java進階篇設計模式之十三 ---- 觀察者模式和空對象模式

来源:https://www.cnblogs.com/xuwujing/archive/2018/11/29/10036204.html
-Advertisement-
Play Games

前言 在 "上一篇" 中我們學習了行為型模式的備忘錄模式(Memento Pattern)和狀態模式(Memento Pattern)。本篇則來學習下行為型模式的最後兩個模式,觀察者模式(Observer Pattern)和空對象模式模式(NullObject Pattern)。 觀察者模式 簡介 ...


前言

上一篇中我們學習了行為型模式的備忘錄模式(Memento Pattern)和狀態模式(Memento Pattern)。本篇則來學習下行為型模式的最後兩個模式,觀察者模式(Observer Pattern)和空對象模式模式(NullObject Pattern)。

觀察者模式

簡介

觀察者模式又叫發佈-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。。
其主要目的是定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。

觀察者模式主要由這四個角色組成,抽象主題角色(Subject)、具體主題角色(ConcreteSubject)、抽象觀察者角色(Observer)和具體觀察者角色(ConcreteObserver)。

  • 抽象主題角色(Subject):它把所有觀察者對象的引用保存到一個聚集里,每個主題都可以有任何數量的觀察者。抽象主題提供一個介面,可以增加和刪除觀察者對象。
  • 具體主題角色(ConcreteSubject):將有關狀態存入具體觀察者對象;在具體主題內部狀態改變時,給所有登記過的觀察者發出通知。
  • 抽象觀察者角色(Observer):主要是負責從備忘錄對象中恢復對象的狀態。

示例圖如下:

在這裡插入圖片描述

我們這裡用一個示例來進行說明吧。
我們在視頻網站進行看劇追番的時候,一般會有一個訂閱功能,如果對某個番劇點了訂閱,那麼該番劇在更新的時候會向訂閱該番劇的用戶推送已經更新的消息,如果取消了訂閱或者沒有訂閱,那麼用戶便不會收到該消息。
那麼我們可以根據這個場景來使用備忘錄模式來進行開發。

首先定義一個抽象主題, 將觀察者(訂閱者)聚集起來,可以進行新增、刪除和通知,這裡就可以當做番劇。
代碼如下:


interface BangumiSubject{
   
   void toThem(UserObserver user);

   void callOff(UserObserver user);

   void notifyUser();
}

然後再定義一個抽象觀察者,有一個主要的方法update,主要是在得到通知時進行更新,這裡就可以當做是用戶。

代碼如下:

interface UserObserver{
   
   void update(String bangumi);
   
   String getName();
}

然後再定義一個具體主題,實現了抽象主題(BangumiSubject)介面的方法,同時通過一個List集合保存觀察者的信息,當需要通知觀察者的時候,遍歷通知即可。
代碼如下:

    class  Bangumi implements BangumiSubject {
        
        private List<UserObserver> list;
        private String  anime;
        public Bangumi(String anime) {
            this.anime = anime;
            list = new ArrayList<UserObserver>();
        }
        
        @Override
        public void toThem(UserObserver user) {
            System.out.println("用戶"+user.getName()+"訂閱了"+anime+"!");
            list.add(user);
        }
        
        @Override
        public void callOff(UserObserver user) {
            if(!list.isEmpty())
                System.out.println("用戶"+user.getName()+"取消訂閱"+anime+"!");
                list.remove(user);
        }
    
        @Override
        public void notifyUser() {
            System.out.println(anime+"更新了!開始通知訂閱該番劇的用戶!");
            list.forEach(user->
                user.update(anime)
            );
        }
    }

最後再定義了一個具體觀察者,實現抽象觀察者(UserObserver)介面的方法。

代碼如下:

    class  User implements UserObserver{
    private String name;
    public User(String name){
        this.name = name;
    }
    
    @Override
    public void update(String bangumi) {
        System.out.println(name+"訂閱的番劇: " + bangumi+"更新啦!");
    }

    @Override
    public String getName() {
        return name;
    } 
}

編寫好之後,那麼我們來進行測試。
這裡我們定義兩個用戶角色,張三和xuwujing,他們都訂閱了<冰菓>和番劇,當番劇更新的時候,他們就會收到通知。 如果他們取消了該番劇的訂閱,那麼他就不會收到該番劇的通知了。

相應的測試代碼如下:

    public static void main(String[] args) {
        String name1 ="張三";
        String name2 ="xuwujing";
        String  bingguo = "冰菓";
        String  fate = "fate/zero";
        BangumiSubject bs1 = new Bangumi(bingguo);
        BangumiSubject bs2 = new Bangumi(fate);
        
        UserObserver uo1 = new User(name1);
        UserObserver uo2 = new User(name2);
        
        //進行訂閱
        bs1.toThem(uo1);
        bs1.toThem(uo2);
        bs2.toThem(uo1);
        bs2.toThem(uo2);
        //進行通知
        bs1.notifyUser();
        bs2.notifyUser();
        
        //取消訂閱
        bs1.callOff(uo1);
        bs2.callOff(uo2);
        //進行通知
        bs1.notifyUser();
        bs2.notifyUser();
}

輸出結果:

        用戶張三訂閱了冰菓!
        用戶xuwujing訂閱了冰菓!
        用戶張三訂閱了fate/zero!
        用戶xuwujing訂閱了fate/zero!
        冰菓更新了!開始通知訂閱該番劇的用戶!
        張三訂閱的番劇: 冰菓更新啦!
        xuwujing訂閱的番劇: 冰菓更新啦!
        fate/zero更新了!開始通知訂閱該番劇的用戶!
        張三訂閱的番劇: fate/zero更新啦!
        xuwujing訂閱的番劇: fate/zero更新啦!
        用戶張三取消訂閱冰菓!
        用戶xuwujing取消訂閱fate/zero!
        冰菓更新了!開始通知訂閱該番劇的用戶!
        xuwujing訂閱的番劇: 冰菓更新啦!
        fate/zero更新了!開始通知訂閱該番劇的用戶!
        張三訂閱的番劇: fate/zero更新啦!

觀察者模式優點:

解除耦合,讓耦合的雙方都依賴於抽象,從而使得各自的變換都不會影響另一邊的變換。

觀察者模式缺點

如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間;
如果在觀察者和觀察目標之間有迴圈依賴的話,觀察目標會觸發它們之間進行迴圈調用,可能導致系統崩潰;
觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。

使用場景:

需要關聯行為的場景;
事件需要創建一個觸發鏈的場景,比如監控;
跨系統的消息交換場景,比如消息隊列、事件匯流排的處理機制。

註意事項:

如果順序執行,某一觀察者錯誤會導致系統卡殼,建議採用非同步方式。

空對象模式

簡介

空對象模式(NullObject Pattern)主要是通過一個空對象取代 NULL 對象實例的檢查。Null 對象不是檢查空值,而是反應一個不做任何動作的關係。 這樣的Null 對象也可以在數據不可用的時候提供預設的行為。
其主要目的是在進行調用是不返回Null,而是返回一個空對象,防止空指針異常。

空對象模式,作為一種被基本遺忘的設計模式,但卻有著不能被遺忘的作用。為什麼說這麼說呢,因為這種模式幾乎難以見到和使用,不是它不夠好用,也不是使用場景少 ,而是相比於簡單的空值判斷,使用它會顯得比較複雜,至於為什麼這麼說,我們可以通過以下示例來進行說明。
假如我們要根據用戶在已存的數據中進行查找相關信息,並且將它的信息給返回回來的話,那麼一般我們是通過該用戶的名稱在資料庫中進行查找,然後將數據返回,但是在資料庫中進行查找時,很有可能沒有該用戶的信息,因此返回Null,如果稍不註意,就會出現空指針異常。這時我們一般的做法是,查詢之後判斷該數據是否為Null,如果為Null,就告知客戶端沒有這條數據,雖然這麼做可以防止空指針異常,但是類似該方法過多,並且返回的信息實體為同一個的時候,我們每次都需要判斷,就有點過於繁瑣。那麼這時我們就可以使用空對象模式來實現這方面的功能。

首先定義一個抽象角色,有獲取姓名和判斷是否為空的方法,這個抽象類的代碼如下:

interface AbstractUser {
   String getName();
   boolean isNull();
}

定義好該抽象類之後,我們再來定義具體實現類。這裡定義兩實現個類,一個表示是真實的用戶,返回真實的姓名,一個是不存在的用戶,用另一種方式返回數據,可以告知客戶端該用戶不存在,預防空指針。
代碼如下:

class RealUser implements AbstractUser {
   private String name;

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

   @Override
   public String getName() {
       return name;
   }

   @Override
   public boolean isNull() {
       return false;
   }
}

class NullUser implements AbstractUser {

   @Override
   public String getName() {
       return "user is not exist";
   }

   @Override
   public boolean isNull() {
       return true;
   }
}

然後在來定義一個工廠角色,用於對客戶端提供一個介面,返回查詢信息。
代碼如下:

class UserFactory {

   public static final String[] names = { "zhangsan", "lisi", "xuwujing" };

   public static AbstractUser getUser(String name) {
       for (int i = 0; i < names.length; i++) {
           if (names[i].equalsIgnoreCase(name)) {
               return new RealUser(name);
           }
       }
       return new NullUser();
   }
}

最後再來進行測試,測試代碼如下:


public static void main(String[] args) {
       AbstractUser au1 = UserFactory.getUser("wangwu");
       AbstractUser au2 = UserFactory.getUser("xuwujing");
       System.out.println(au1.isNull());
       System.out.println(au1.getName());
       System.out.println(au2.isNull());
       System.out.println(au2.getName());
}

輸出結果:

true
user is not exist
false
xuwujing

空對象優點:

可以加強系統的穩固性,能有效防止空指針報錯對整個系統的影響;
不依賴客戶端便可以保證系統的穩定性;

空對象缺點:

需要編寫較多的代碼來實現空值的判斷,從某種方面來說不划算;

使用場景:

需要大量對空值進行判斷的時候;

其它

音樂推薦

分享一首很有節奏感的電音!

項目的代碼

java-study是本人在學習Java過程中記錄的一些代碼,也包括之前博文中使用的代碼。如果感覺不錯,希望順手給個start,當然如果有不足,也希望提出。
github地址: https://github.com/xuwujing/java-study

原創不易,如果感覺不錯,希望給個推薦!您的支持是我寫作的最大動力!
版權聲明:
作者:虛無境
博客園出處:http://www.cnblogs.com/xuwujing
CSDN出處:http://blog.csdn.net/qazwsxpcm 
個人博客出處:http://www.panchengming.com


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • oracle:11.2.0.4 linux:紅帽6.5 依賴包,是全部都寫上來了,按照需求選擇,也可以全部都安裝一遍 yum install -y binutils-* compat-libstdc++-33-* elfutils-libelf-* elfutils-libelf-devel-* g ...
  • linux centOs中安裝好資料庫,客戶端用plsql連接oracle ...
  • 一、簡介: ScrollView,通過官方文檔的繼承關係可以看出,它繼承自FrameLayout,所以它是一種特殊類型的FrameLayout,因為它可以使用用戶滾動顯示一個占據的空間大於物理顯示的視圖列表。值得註意的是,ScrollView只能包含一個子視圖或視圖組,在實際項目中,通常包含的是一個 ...
  • 一、簡介: GridView是一個以表格形式顯示多張圖片等組件。它是按照行列的方式來顯示內容的,比如實現九宮格圖,用GridView是首選。 <! more 二、代碼塊: 看過我上一篇博客的同學應該知道,一步一步全部步驟寫出來是很費時間的,大概流程就那樣,所以這次網格視圖就直接上代碼塊了,步驟差不多 ...
  • 緣起 前端開發離不開Chrome的開發者工具,尤其是調試Android WebView時。然而,如果使用chrome://Inspect的方法,國內的開發者會驚奇地發現“空白啊”!為此,我發佈過這個離線包的解決方案!已經可以無需Fan牆就能調試了。但是,在使用過程中發現了以下問題: 解決 基於以上問 ...
  • nonatomic : 非原子屬性 atomic : 原子屬性 如果不寫關鍵字 那麼預設就是 原子屬性 - 多線程寫入屬性時,保證同一時間只有一個線程能夠執行寫入操作 - 單(線程)寫多(線程)讀線程技術,同樣有可能出現"臟數據",重新讀一下 - 性能較慢 線程安全 在多個線程進行讀寫操作的時候,仍 ...
  • 單例模式是設計模式中比較常見簡單的一種,典型雙重檢測寫法如下: 接下來對該寫法進行分析,為何這樣寫? 一、為何要同步: 多線程情況下,若是A線程調用getInstance,發現instance為null,那麼它會開始創建實例,如果此時CPU發生時間片切換,線程B開始執行,調用getInstance, ...
  • 數據持久化,也就是把數據保存到磁碟,以後可以再讀取出來使用(也可以再次更改或刪除)。很多場景需要數據持久化,比如為了減輕伺服器的訪問與存儲壓力,客戶端需要在本地做一些數據持久化的工作。iOS的數據持久化,有幾種方式,包括:自定義格式的文件、plistCoreData、FMDB等等。這裡記錄基於Cor... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...