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

前言

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

觀察者模式

簡介java

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

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

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

示例圖以下:數據庫

在這裏插入圖片描述

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

首先定義一個抽象主題, 將觀察者(訂閱者)彙集起來,能夠進行新增、刪除和通知,這裏就能夠當作番劇。
代碼以下:異步

interface BangumiSubject{
   
   void toThem(UserObserver user);

   void callOff(UserObserver user);

   void notifyUser();
}

而後再定義一個抽象觀察者,有一個主要的方法update,主要是在獲得通知時進行更新,這裏就能夠當作是用戶。ide

代碼以下:學習

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

相關文章
相關標籤/搜索