【學(xué)習(xí)難度:★★★☆☆,使用頻率:★★★★★】
直接出處:觀察者模式
梳理和學(xué)習(xí):https://github.com/BruceOuyang/boy-design-pattern
簡書日期: 2018/03/27
簡書首頁:http://www.reibang.com/p/0fb891a7c5ed
對(duì)象間的聯(lián)動(dòng)——觀察者模式(一)
觀察者模式是設(shè)計(jì)模式中的“超級(jí)模式”晒奕,其應(yīng)用隨處可見,在之后幾篇文章里,我將向大家詳細(xì)介紹觀察者模式脑慧。
“紅燈停魄眉,綠燈行”,在日常生活中闷袒,交通信號(hào)燈裝點(diǎn)著我們的城市坑律,指揮著日益擁擠的城市交通。當(dāng)紅燈亮起囊骤,來往的汽車將停止晃择;而綠燈亮起,汽車可以繼續(xù)前行也物。在這個(gè)過程中宫屠,交通信號(hào)燈是汽車(更準(zhǔn)確地說應(yīng)該是汽車駕駛員)的觀察目標(biāo),而汽車是觀察者滑蚯。隨著交通信號(hào)燈的變化浪蹂,汽車的行為也將隨之而變化,一盞交通信號(hào)燈可以指揮多輛汽車告材。如圖22-1所示:
在軟件系統(tǒng)中坤次,有些對(duì)象之間也存在類似交通信號(hào)燈和汽車之間的關(guān)系,一個(gè)對(duì)象的狀態(tài)或行為的變化將導(dǎo)致其他對(duì)象的狀態(tài)或行為也發(fā)生改變斥赋,它們之間將產(chǎn)生聯(lián)動(dòng)缰猴,正所謂“觸一而牽百發(fā)”。為了更好地描述對(duì)象之間存在的這種一對(duì)多(包括一對(duì)一)的聯(lián)動(dòng)疤剑,觀察者模式應(yīng)運(yùn)而生滑绒,它定義了對(duì)象之間一種一對(duì)多的依賴關(guān)系,讓一個(gè)對(duì)象的改變能夠影響其他對(duì)象隘膘。本章我們將學(xué)習(xí)用于實(shí)現(xiàn)對(duì)象間聯(lián)動(dòng)的觀察者模式疑故。
22.1 多人聯(lián)機(jī)對(duì)戰(zhàn)游戲的設(shè)計(jì)
Sunny軟件公司欲開發(fā)一款多人聯(lián)機(jī)對(duì)戰(zhàn)游戲(類似魔獸世界、星際爭霸等游戲)棘幸,在該游戲中,多個(gè)玩家可以加入同一戰(zhàn)隊(duì)組成聯(lián)盟倦零,當(dāng)戰(zhàn)隊(duì)中某一成員受到敵人攻擊時(shí)將給所有其他盟友發(fā)送通知误续,盟友收到通知后將作出響應(yīng)。
Sunny軟件公司開發(fā)人員需要提供一個(gè)設(shè)計(jì)方案來實(shí)現(xiàn)戰(zhàn)隊(duì)成員之間的聯(lián)動(dòng)扫茅。
Sunny軟件公司開發(fā)人員通過對(duì)系統(tǒng)功能需求進(jìn)行分析蹋嵌,發(fā)現(xiàn)在該系統(tǒng)中戰(zhàn)隊(duì)成員之間的聯(lián)動(dòng)過程可以簡單描述如下:
聯(lián)盟成員受到攻擊-->發(fā)送通知給盟友-->盟友作出響應(yīng)。
如果按照上述思路來設(shè)計(jì)系統(tǒng)葫隙,由于聯(lián)盟成員在受到攻擊時(shí)需要通知他的每一個(gè)盟友栽烂,因此每個(gè)聯(lián)盟成員都需要持有其他所有盟友的信息,這將導(dǎo)致系統(tǒng)開銷較大,因此Sunny公司開發(fā)人員決定引入一個(gè)新的角色——“戰(zhàn)隊(duì)控制中心”——來負(fù)責(zé)維護(hù)和管理每個(gè)戰(zhàn)隊(duì)所有成員的信息腺办。當(dāng)一個(gè)聯(lián)盟成員受到攻擊時(shí)焰手,將向相應(yīng)的戰(zhàn)隊(duì)控制中心發(fā)送求助信息,戰(zhàn)隊(duì)控制中心再逐一通知每個(gè)盟友怀喉,盟友再作出響應(yīng)书妻,如圖22-2所示:
在圖22-2中,受攻擊的聯(lián)盟成員將與戰(zhàn)隊(duì)控制中心產(chǎn)生聯(lián)動(dòng)躬拢,戰(zhàn)隊(duì)控制中心還將與其他盟友產(chǎn)生聯(lián)動(dòng)躲履。
如何實(shí)現(xiàn)對(duì)象之間的聯(lián)動(dòng)?如何讓一個(gè)對(duì)象的狀態(tài)或行為改變時(shí)聊闯,依賴于它的對(duì)象能夠得到通知并進(jìn)行相應(yīng)的處理工猜?
別著急,本章所介紹的觀察者模式將為對(duì)象之間的聯(lián)動(dòng)提供一個(gè)優(yōu)秀的解決方案菱蔬,下面就讓我們正式進(jìn)入觀察者模式的學(xué)習(xí)篷帅。
對(duì)象間的聯(lián)動(dòng)——觀察者模式(二)
22.2 觀察者模式概述
觀察者模式是使用頻率最高的設(shè)計(jì)模式之一,它用于建立一種對(duì)象與對(duì)象之間的依賴關(guān)系汗销,一個(gè)對(duì)象發(fā)生改變時(shí)將自動(dòng)通知其他對(duì)象犹褒,其他對(duì)象將相應(yīng)作出反應(yīng)。在觀察者模式中弛针,發(fā)生改變的對(duì)象稱為觀察目標(biāo)叠骑,而被通知的對(duì)象稱為觀察者,一個(gè)觀察目標(biāo)可以對(duì)應(yīng)多個(gè)觀察者削茁,而且這些觀察者之間可以沒有任何相互聯(lián)系宙枷,可以根據(jù)需要增加和刪除觀察者,使得系統(tǒng)更易于擴(kuò)展茧跋。
觀察者模式定義如下: 觀察者模式(Observer Pattern):定義對(duì)象之間的一種一對(duì)多依賴關(guān)系慰丛,使得每當(dāng)一個(gè)對(duì)象狀態(tài)發(fā)生改變時(shí),其相關(guān)依賴對(duì)象皆得到通知并被自動(dòng)更新瘾杭。觀察者模式的別名包括發(fā)布-訂閱(Publish/Subscribe)模式诅病、模型-視圖(Model/View)模式、源-監(jiān)聽器(Source/Listener)模式或從屬者(Dependents)模式粥烁。觀察者模式是一種對(duì)象行為型模式贤笆。
觀察者模式結(jié)構(gòu)中通常包括觀察目標(biāo)和觀察者兩個(gè)繼承層次結(jié)構(gòu),其結(jié)構(gòu)如圖22-3所示:
在觀察者模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:
Subject(目標(biāo)):目標(biāo)又稱為主題讨阻,它是指被觀察的對(duì)象芥永。在目標(biāo)中定義了一個(gè)觀察者集合,一個(gè)觀察目標(biāo)可以接受任意數(shù)量的觀察者來觀察钝吮,它提供一系列方法來增加和刪除觀察者對(duì)象埋涧,同時(shí)它定義了通知方法notify()板辽。目標(biāo)類可以是接口,也可以是抽象類或具體類棘催。
ConcreteSubject(具體目標(biāo)):具體目標(biāo)是目標(biāo)類的子類劲弦,通常它包含有經(jīng)常發(fā)生改變的數(shù)據(jù),當(dāng)它的狀態(tài)發(fā)生改變時(shí)巧鸭,向它的各個(gè)觀察者發(fā)出通知瓶您;同時(shí)它還實(shí)現(xiàn)了在目標(biāo)類中定義的抽象業(yè)務(wù)邏輯方法(如果有的話)。如果無須擴(kuò)展目標(biāo)類纲仍,則具體目標(biāo)類可以省略呀袱。
Observer(觀察者):觀察者將對(duì)觀察目標(biāo)的改變做出反應(yīng),觀察者一般定義為接口郑叠,該接口聲明了更新數(shù)據(jù)的方法update()夜赵,因此又稱為抽象觀察者。
ConcreteObserver(具體觀察者):在具體觀察者中維護(hù)一個(gè)指向具體目標(biāo)對(duì)象的引用乡革,它存儲(chǔ)具體觀察者的有關(guān)狀態(tài)寇僧,這些狀態(tài)需要和具體目標(biāo)的狀態(tài)保持一致;它實(shí)現(xiàn)了在抽象觀察者Observer中定義的update()方法沸版。通常在實(shí)現(xiàn)時(shí)嘁傀,可以調(diào)用具體目標(biāo)類的attach()方法將自己添加到目標(biāo)類的集合中或通過detach()方法將自己從目標(biāo)類的集合中刪除。
觀察者模式描述了如何建立對(duì)象與對(duì)象之間的依賴關(guān)系视粮,以及如何構(gòu)造滿足這種需求的系統(tǒng)细办。觀察者模式包含觀察目標(biāo)和觀察者兩類對(duì)象,一個(gè)目標(biāo)可以有任意數(shù)目的與之相依賴的觀察者蕾殴,一旦觀察目標(biāo)的狀態(tài)發(fā)生改變笑撞,所有的觀察者都將得到通知。作為對(duì)這個(gè)通知的響應(yīng)钓觉,每個(gè)觀察者都將監(jiān)視觀察目標(biāo)的狀態(tài)以使其狀態(tài)與目標(biāo)狀態(tài)同步茴肥,這種交互也稱為發(fā)布-訂閱(Publish-Subscribe)。觀察目標(biāo)是通知的發(fā)布者荡灾,它發(fā)出通知時(shí)并不需要知道誰是它的觀察者瓤狐,可以有任意數(shù)目的觀察者訂閱它并接收通知。 下面通過示意代碼來對(duì)該模式進(jìn)行進(jìn)一步分析批幌。首先我們定義一個(gè)抽象目標(biāo)Subject础锐,典型代碼如下所示:
import java.util.*;
abstract class Subject {
//定義一個(gè)觀察者集合用于存儲(chǔ)所有觀察者對(duì)象
protected ArrayList observers<Observer> = new ArrayList();
//注冊(cè)方法,用于向觀察者集合中增加一個(gè)觀察者
public void attach(Observer observer) {
observers.add(observer);
}
//注銷方法逼裆,用于在觀察者集合中刪除一個(gè)觀察者
public void detach(Observer observer) {
observers.remove(observer);
}
//聲明抽象通知方法
public abstract void notify();
}
具體目標(biāo)類ConcreteSubject是實(shí)現(xiàn)了抽象目標(biāo)類Subject的一個(gè)具體子類郁稍,其典型代碼如下所示:
class ConcreteSubject extends Subject {
//實(shí)現(xiàn)通知方法
public void notify() {
//遍歷觀察者集合赦政,調(diào)用每一個(gè)觀察者的響應(yīng)方法
for(Object obs:observers) {
((Observer)obs).update();
}
}
}
抽象觀察者角色一般定義為一個(gè)接口胜宇,通常只聲明一個(gè)update()方法耀怜,為不同觀察者的更新(響應(yīng))行為定義相同的接口,這個(gè)方法在其子類中實(shí)現(xiàn)桐愉,不同的觀察者具有不同的響應(yīng)方法财破。抽象觀察者Observer典型代碼如下所示:
interface Observer {
//聲明響應(yīng)方法
public void update();
}
在具體觀察者ConcreteObserver中實(shí)現(xiàn)了update()方法,其典型代碼如下所示:
class ConcreteObserver implements Observer {
//實(shí)現(xiàn)響應(yīng)方法
public void update() {
//具體響應(yīng)代碼
}
}
在有些更加復(fù)雜的情況下从诲,具體觀察者類ConcreteObserver的update()方法在執(zhí)行時(shí)需要使用到具體目標(biāo)類ConcreteSubject中的狀態(tài)(屬性)左痢,因此在ConcreteObserver與ConcreteSubject之間有時(shí)候還存在關(guān)聯(lián)或依賴關(guān)系,在ConcreteObserver中定義一個(gè)ConcreteSubject實(shí)例系洛,通過該實(shí)例獲取存儲(chǔ)在ConcreteSubject中的狀態(tài)俊性。如果ConcreteObserver的update()方法不需要使用到ConcreteSubject中的狀態(tài)屬性,則可以對(duì)觀察者模式的標(biāo)準(zhǔn)結(jié)構(gòu)進(jìn)行簡化描扯,在具體觀察者ConcreteObserver和具體目標(biāo)ConcreteSubject之間無須維持對(duì)象引用定页。如果在具體層具有關(guān)聯(lián)關(guān)系,系統(tǒng)的擴(kuò)展性將受到一定的影響绽诚,增加新的具體目標(biāo)類有時(shí)候需要修改原有觀察者的代碼典徊,在一定程度上違反了“開閉原則”,但是如果原有觀察者類無須關(guān)聯(lián)新增的具體目標(biāo)恩够,則系統(tǒng)擴(kuò)展性不受影響卒落。
思考
觀察者模式是否符合“開閉原則”?【從增加具體觀察者和增加具體目標(biāo)類兩方面考慮蜂桶±鼙希】
對(duì)象間的聯(lián)動(dòng)——觀察者模式(三)
23.3 完整解決方案
為了實(shí)現(xiàn)對(duì)象之間的聯(lián)動(dòng),Sunny軟件公司開發(fā)人員決定使用觀察者模式來進(jìn)行多人聯(lián)機(jī)對(duì)戰(zhàn)游戲的設(shè)計(jì)屎飘,其基本結(jié)構(gòu)如圖22-4所示:
在圖22-4中妥曲,AllyControlCenter充當(dāng)目標(biāo)類,ConcreteAllyControlCenter充當(dāng)具體目標(biāo)類钦购,Observer充當(dāng)抽象觀察者檐盟,Player充當(dāng)具體觀察者。完整代碼如下所示:
import java.util.*;
//抽象觀察類
interface Observer {
public String getName();
public void setName(String name);
public void help(); //聲明支援盟友方法
public void beAttacked(AllyControlCenter acc); //聲明遭受攻擊方法
}
//戰(zhàn)隊(duì)成員類:具體觀察者類
class Player implements Observer {
private String name;
public Player(String name) {
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
//支援盟友方法的實(shí)現(xiàn)
public void help() {
System.out.println("堅(jiān)持住押桃," + this.name + "來救你葵萎!");
}
//遭受攻擊方法的實(shí)現(xiàn),當(dāng)遭受攻擊時(shí)將調(diào)用戰(zhàn)隊(duì)控制中心類的通知方法notifyObserver()來通知盟友
public void beAttacked(AllyControlCenter acc) {
System.out.println(this.name + "被攻擊唱凯!");
acc.notifyObserver(name);
}
}
//戰(zhàn)隊(duì)控制中心類:目標(biāo)類
abstract class AllyControlCenter {
protected String allyName; //戰(zhàn)隊(duì)名稱
protected ArrayList<Observer> players = new ArrayList<Observer>(); //定義一個(gè)集合用于存儲(chǔ)戰(zhàn)隊(duì)成員
public void setAllyName(String allyName) {
this.allyName = allyName;
}
public String getAllyName() {
return this.allyName;
}
//注冊(cè)方法
public void join(Observer obs) {
System.out.println(obs.getName() + "加入" + this.allyName + "戰(zhàn)隊(duì)羡忘!");
players.add(obs);
}
//注銷方法
public void quit(Observer obs) {
System.out.println(obs.getName() + "退出" + this.allyName + "戰(zhàn)隊(duì)!");
players.remove(obs);
}
//聲明抽象通知方法
public abstract void notifyObserver(String name);
}
//具體戰(zhàn)隊(duì)控制中心類:具體目標(biāo)類
class ConcreteAllyControlCenter extends AllyControlCenter {
public ConcreteAllyControlCenter(String allyName) {
System.out.println(allyName + "戰(zhàn)隊(duì)組建成功磕昼!");
System.out.println("----------------------------");
this.allyName = allyName;
}
//實(shí)現(xiàn)通知方法
public void notifyObserver(String name) {
System.out.println(this.allyName + "戰(zhàn)隊(duì)緊急通知卷雕,盟友" + name + "遭受敵人攻擊!");
//遍歷觀察者集合票从,調(diào)用每一個(gè)盟友(自己除外)的支援方法
for(Object obs : players) {
if (!((Observer)obs).getName().equalsIgnoreCase(name)) {
((Observer)obs).help();
}
}
}
}
編寫如下客戶端測試代碼:
class Client {
public static void main(String args[]) {
//定義觀察目標(biāo)對(duì)象
AllyControlCenter acc;
acc = new ConcreteAllyControlCenter("金庸群俠");
//定義四個(gè)觀察者對(duì)象
Observer player1,player2,player3,player4;
player1 = new Player("楊過");
acc.join(player1);
player2 = new Player("令狐沖");
acc.join(player2);
player3 = new Player("張無忌");
acc.join(player3);
player4 = new Player("段譽(yù)");
acc.join(player4);
//某成員遭受攻擊
Player1.beAttacked(acc);
}
}
編譯并運(yùn)行程序漫雕,輸出結(jié)果如下:
金庸群俠戰(zhàn)隊(duì)組建成功滨嘱!
----------------------------
楊過加入金庸群俠戰(zhàn)隊(duì)!
令狐沖加入金庸群俠戰(zhàn)隊(duì)浸间!
張無忌加入金庸群俠戰(zhàn)隊(duì)太雨!
段譽(yù)加入金庸群俠戰(zhàn)隊(duì)!
楊過被攻擊魁蒜!
金庸群俠戰(zhàn)隊(duì)緊急通知囊扳,盟友楊過遭受敵人攻擊!
堅(jiān)持住兜看,令狐沖來救你锥咸!
堅(jiān)持住,張無忌來救你细移!
堅(jiān)持住她君,段譽(yù)來救你!
在本實(shí)例中葫哗,實(shí)現(xiàn)了兩次對(duì)象之間的聯(lián)動(dòng)缔刹,當(dāng)一個(gè)游戲玩家Player對(duì)象的beAttacked()方法被調(diào)用時(shí),將調(diào)用AllyControlCenter的notifyObserver()方法來進(jìn)行處理劣针,而在notifyObserver()方法中又將調(diào)用其他Player對(duì)象的help()方法校镐。Player的beAttacked()方法、AllyControlCenter的notifyObserver()方法以及Player的help()方法構(gòu)成了一個(gè)聯(lián)動(dòng)觸發(fā)鏈捺典,執(zhí)行順序如下所示:
Player.beAttacked() --> AllyControlCenter.notifyObserver() -->Player.help()鸟廓。
對(duì)象間的聯(lián)動(dòng)——觀察者模式(四)
22.4 JDK對(duì)觀察者模式的支持
觀察者模式在Java語言中的地位非常重要。在JDK的java.util包中襟己,提供了Observable類以及Observer接口引谜,它們構(gòu)成了JDK對(duì)觀察者模式的支持。如圖22-5所示:
(1) Observer接口
在java.util.Observer接口中只聲明一個(gè)方法擎浴,它充當(dāng)抽象觀察者员咽,其方法聲明代碼如下所示:
void update(Observable o, Object arg);
當(dāng)觀察目標(biāo)的狀態(tài)發(fā)生變化時(shí),該方法將會(huì)被調(diào)用贮预,在Observer的子類中將實(shí)現(xiàn)update()方法贝室,即具體觀察者可以根據(jù)需要具有不同的更新行為。當(dāng)調(diào)用觀察目標(biāo)類Observable的notifyObservers()方法時(shí)仿吞,將執(zhí)行觀察者類中的update()方法滑频。
(2) Observable類
java.util.Observable類充當(dāng)觀察目標(biāo)類,在Observable中定義了一個(gè)向量Vector來存儲(chǔ)觀察者對(duì)象唤冈,它所包含的方法及說明見表22-1:
我們可以直接使用Observer接口和Observable類來作為觀察者模式的抽象層峡迷,再自定義具體觀察者類和具體觀察目標(biāo)類,通過使用JDK中的Observer接口和Observable類你虹,可以更加方便地在Java語言中應(yīng)用觀察者模式绘搞。
對(duì)象間的聯(lián)動(dòng)——觀察者模式(五)
22.5 觀察者模式與Java事件處理
JDK 1.0及更早版本的事件模型基于職責(zé)鏈模式枣申,但是這種模型不適用于復(fù)雜的系統(tǒng)看杭,因此在JDK 1.1及以后的各個(gè)版本中,事件處理模型采用基于觀察者模式的委派事件模型(DelegationEvent Model, DEM)挟伙,即一個(gè)Java組件所引發(fā)的事件并不由引發(fā)事件的對(duì)象自己來負(fù)責(zé)處理尖阔,而是委派給獨(dú)立的事件處理對(duì)象負(fù)責(zé)贮缅。
在DEM模型中,目標(biāo)角色(如界面組件)負(fù)責(zé)發(fā)布事件介却,而觀察者角色(事件處理者)可以向目標(biāo)訂閱它所感興趣的事件谴供。當(dāng)一個(gè)具體目標(biāo)產(chǎn)生一個(gè)事件時(shí),它將通知所有訂閱者齿坷。事件的發(fā)布者稱為事件源(Event Source)桂肌,而訂閱者稱為事件監(jiān)聽器(Event Listener)忽刽,在這個(gè)過程中還可以通過事件對(duì)象(Event Object)來傳遞與事件相關(guān)的信息诱贿,可以在事件監(jiān)聽者的實(shí)現(xiàn)類中實(shí)現(xiàn)事件處理竣付,因此事件監(jiān)聽對(duì)象又可以稱為事件處理對(duì)象登澜。事件源對(duì)象恕刘、事件監(jiān)聽對(duì)象(事件處理對(duì)象)和事件對(duì)象構(gòu)成了Java事件處理模型的三要素撬碟。事件源對(duì)象充當(dāng)觀察目標(biāo)丽声,而事件監(jiān)聽對(duì)象充當(dāng)觀察者乎澄。以按鈕點(diǎn)擊事件為例李滴,其事件處理流程如下:
(1) 如果用戶在GUI中單擊一個(gè)按鈕螃宙,將觸發(fā)一個(gè)事件(如ActionEvent類型的動(dòng)作事件),JVM將產(chǎn)生一個(gè)相應(yīng)的ActionEvent類型的事件對(duì)象所坯,在該事件對(duì)象中包含了有關(guān)事件和事件源的信息谆扎,此時(shí)按鈕是事件源對(duì)象;
(2) 將ActionEvent事件對(duì)象傳遞給事件監(jiān)聽對(duì)象(事件處理對(duì)象)芹助,JDK提供了專門用于處理ActionEvent事件的接口ActionListener燕酷,開發(fā)人員需提供一個(gè)ActionListener的實(shí)現(xiàn)類(如MyActionHandler),實(shí)現(xiàn)在ActionListener接口中聲明的抽象事件處理方法actionPerformed()周瞎,對(duì)所發(fā)生事件做出相應(yīng)的處理苗缩;
(3) 開發(fā)人員將ActionListener接口的實(shí)現(xiàn)類(如MyActionHandler)對(duì)象注冊(cè)到按鈕中,可以通過按鈕類的addActionListener()方法來實(shí)現(xiàn)注冊(cè)声诸;
(4) JVM在觸發(fā)事件時(shí)將調(diào)用按鈕的fireXXX()方法酱讶,在該方法內(nèi)部將調(diào)用注冊(cè)到按鈕中的事件處理對(duì)象的actionPerformed()方法,實(shí)現(xiàn)對(duì)事件的處理彼乌。
使用類似的方法泻肯,我們可自定義GUI組件渊迁,如包含兩個(gè)文本框和兩個(gè)按鈕的登錄組件LoginBean,可以采用如圖22-6所示設(shè)計(jì)方案:
圖22-6中相關(guān)類說明如下:
(1) LoginEvent是事件類琉朽,它用于封裝與事件有關(guān)的信息,它不是觀察者模式的一部分稚铣,但是它可以在目標(biāo)對(duì)象和觀察者對(duì)象之間傳遞數(shù)據(jù)箱叁,在AWT事件模型中,所有的自定義事件類都是java.util.EventObject的子類惕医。
(2) LoginEventListener充當(dāng)抽象觀察者耕漱,它聲明了事件響應(yīng)方法validateLogin(),用于處理事件抬伺,該方法也稱為事件處理方法螟够,validateLogin()方法將一個(gè)LoginEvent類型的事件對(duì)象作為參數(shù),用于傳輸與事件相關(guān)的數(shù)據(jù)峡钓,在其子類中實(shí)現(xiàn)該方法妓笙,實(shí)現(xiàn)具體的事件處理。
(3) LoginBean充當(dāng)具體目標(biāo)類能岩,在這里我們沒有定義抽象目標(biāo)類给郊,對(duì)觀察者模式進(jìn)行了一定的簡化。在LoginBean中定義了抽象觀察者LoginEventListener類型的對(duì)象lel和事件對(duì)象LoginEvent捧灰,提供了注冊(cè)方法addLoginEventListener()用于添加觀察者淆九,在Java事件處理中,通常使用的是一對(duì)一的觀察者模式毛俏,而不是一對(duì)多的觀察者模式炭庙,也就是說,一個(gè)觀察目標(biāo)中只定義一個(gè)觀察者對(duì)象煌寇,而不是提供一個(gè)觀察者對(duì)象的集合焕蹄。在LoginBean中還定義了通知方法fireLoginEvent(),該方法在Java事件處理模型中稱為“點(diǎn)火方法”阀溶,在該方法內(nèi)部實(shí)例化了一個(gè)事件對(duì)象LoginEvent腻脏,將用戶輸入的信息傳給觀察者對(duì)象,并且調(diào)用了觀察者對(duì)象的響應(yīng)方法validateLogin()银锻。
(4) LoginValidatorA和LoginValidatorB充當(dāng)具體觀察者類永品,它們實(shí)現(xiàn)了在LoginEventListener接口中聲明的抽象方法validateLogin(),用于具體實(shí)現(xiàn)事件處理击纬,該方法包含一個(gè)LoginEvent類型的參數(shù)鼎姐,在LoginValidatorA和LoginValidatorB類中可以針對(duì)相同的事件提供不同的實(shí)現(xiàn)。
對(duì)象間的聯(lián)動(dòng)——觀察者模式(六)
22.6 觀察者模式與MVC
在當(dāng)前流行的MVC(Model-View-Controller)架構(gòu)中也應(yīng)用了觀察者模式,MVC是一種架構(gòu)模式炕桨,它包含三個(gè)角色:模型(Model)饭尝,視圖(View)和控制器(Controller)。其中模型可對(duì)應(yīng)于觀察者模式中的觀察目標(biāo)献宫,而視圖對(duì)應(yīng)于觀察者钥平,控制器可充當(dāng)兩者之間的中介者。當(dāng)模型層的數(shù)據(jù)發(fā)生改變時(shí)姊途,視圖層將自動(dòng)改變其顯示內(nèi)容涉瘾。如圖22-7所示:
在圖22-7中,模型層提供的數(shù)據(jù)是視圖層所觀察的對(duì)象吭净,在視圖層中包含兩個(gè)用于顯示數(shù)據(jù)的圖表對(duì)象,一個(gè)是柱狀圖肴甸,一個(gè)是餅狀圖寂殉,相同的數(shù)據(jù)擁有不同的圖表顯示方式,如果模型層的數(shù)據(jù)發(fā)生改變原在,兩個(gè)圖表對(duì)象將隨之發(fā)生變化友扰,這意味著圖表對(duì)象依賴模型層提供的數(shù)據(jù)對(duì)象,因此數(shù)據(jù)對(duì)象的任何狀態(tài)改變都應(yīng)立即通知它們庶柿。同時(shí)村怪,這兩個(gè)圖表之間相互獨(dú)立,不存在任何聯(lián)系浮庐,而且圖表對(duì)象的個(gè)數(shù)沒有任何限制甚负,用戶可以根據(jù)需要再增加新的圖表對(duì)象,如折線圖审残。在增加新的圖表對(duì)象時(shí)梭域,無須修改原有類庫,滿足“開閉原則”搅轿。
擴(kuò)展
大家可以查閱相關(guān)資料對(duì)MVC模式進(jìn)行深入學(xué)習(xí)病涨,如Oracle公司提供的技術(shù)文檔《Java SE Application Design With MVC》,參考鏈接:http://www.oracle.com/technetwork/articles/javase/index-142890.html璧坟。
22.7 觀察者模式總結(jié)
觀察者模式是一種使用頻率非常高的設(shè)計(jì)模式既穆,無論是移動(dòng)應(yīng)用、Web應(yīng)用或者桌面應(yīng)用雀鹃,觀察者模式幾乎無處不在幻工,它為實(shí)現(xiàn)對(duì)象之間的聯(lián)動(dòng)提供了一套完整的解決方案,凡是涉及到一對(duì)一或者一對(duì)多的對(duì)象交互場景都可以使用觀察者模式黎茎。觀察者模式廣泛應(yīng)用于各種編程語言的GUI事件處理的實(shí)現(xiàn)会钝,在基于事件的XML解析技術(shù)(如SAX2)以及Web事件處理中也都使用了觀察者模式。
1.主要優(yōu)點(diǎn)
觀察者模式的主要優(yōu)點(diǎn)如下:
(1) 觀察者模式可以實(shí)現(xiàn)表示層和數(shù)據(jù)邏輯層的分離,定義了穩(wěn)定的消息更新傳遞機(jī)制迁酸,并抽象了更新接口先鱼,使得可以有各種各樣不同的表示層充當(dāng)具體觀察者角色。
(2) 觀察者模式在觀察目標(biāo)和觀察者之間建立一個(gè)抽象的耦合奸鬓。觀察目標(biāo)只需要維持一個(gè)抽象觀察者的集合焙畔,無須了解其具體觀察者。由于觀察目標(biāo)和觀察者沒有緊密地耦合在一起串远,因此它們可以屬于不同的抽象化層次宏多。
(3) 觀察者模式支持廣播通信,觀察目標(biāo)會(huì)向所有已注冊(cè)的觀察者對(duì)象發(fā)送通知澡罚,簡化了一對(duì)多系統(tǒng)設(shè)計(jì)的難度伸但。
(4) 觀察者模式滿足“開閉原則”的要求,增加新的具體觀察者無須修改原有系統(tǒng)代碼留搔,在具體觀察者與觀察目標(biāo)之間不存在關(guān)聯(lián)關(guān)系的情況下更胖,增加新的觀察目標(biāo)也很方便。
2.主要缺點(diǎn)
觀察者模式的主要缺點(diǎn)如下:
(1) 如果一個(gè)觀察目標(biāo)對(duì)象有很多直接和間接觀察者隔显,將所有的觀察者都通知到會(huì)花費(fèi)很多時(shí)間却妨。
(2) 如果在觀察者和觀察目標(biāo)之間存在循環(huán)依賴,觀察目標(biāo)會(huì)觸發(fā)它們之間進(jìn)行循環(huán)調(diào)用括眠,可能導(dǎo)致系統(tǒng)崩潰彪标。
(3) 觀察者模式?jīng)]有相應(yīng)的機(jī)制讓觀察者知道所觀察的目標(biāo)對(duì)象是怎么發(fā)生變化的,而僅僅只是知道觀察目標(biāo)發(fā)生了變化掷豺。
3.適用場景
在以下情況下可以考慮使用觀察者模式:
(1) 一個(gè)抽象模型有兩個(gè)方面捞烟,其中一個(gè)方面依賴于另一個(gè)方面,將這兩個(gè)方面封裝在獨(dú)立的對(duì)象中使它們可以各自獨(dú)立地改變和復(fù)用当船。
(2) 一個(gè)對(duì)象的改變將導(dǎo)致一個(gè)或多個(gè)其他對(duì)象也發(fā)生改變坷襟,而并不知道具體有多少對(duì)象將發(fā)生改變,也不知道這些對(duì)象是誰生年。
(3) 需要在系統(tǒng)中創(chuàng)建一個(gè)觸發(fā)鏈婴程,A對(duì)象的行為將影響B(tài)對(duì)象,B對(duì)象的行為將影響C對(duì)象……抱婉,可以使用觀察者模式創(chuàng)建一種鏈?zhǔn)接|發(fā)機(jī)制档叔。
練習(xí)
Sunny軟件公司欲開發(fā)一款實(shí)時(shí)在線股票軟件,該軟件需提供如下功能:當(dāng)股票購買者所購買的某支股票價(jià)格變化幅度達(dá)到5%時(shí)蒸绩,系統(tǒng)將自動(dòng)發(fā)送通知(包括新價(jià)格)給購買該股票的所有股民衙四。試使用觀察者模式設(shè)計(jì)并實(shí)現(xiàn)該系統(tǒng)。
練習(xí)會(huì)在我的github上做掉