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

虛無境發表於2018-11-29

前言

上一篇中我們學習了行為型模式的備忘錄模式(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,他們都訂閱了<冰菓>和<fate/zero>番劇,當番劇更新的時候,他們就會收到通知。 如果他們取消了該番劇的訂閱,那麼他就不會收到該番劇的通知了。

相應的測試程式碼如下:

	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地址: github.com/xuwujing/ja…

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

相關文章