前言 在 "上一篇" 中我們學習了行為型模式的備忘錄模式(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