前言
提起觀察者模式,LZ略微有點(diǎn)小小的激動(dòng),因?yàn)長(zhǎng)Z在工作中接觸的第一個(gè)項(xiàng)目就用到了觀察者模式,雖然LZ當(dāng)時(shí)處于一種懵逼狀態(tài),完全是復(fù)制粘貼代碼去完成業(yè)務(wù)需求,但那個(gè)項(xiàng)目的接觸,也讓LZ對(duì)觀察者模式有了一個(gè)初步的了解,此后一直對(duì)觀察者模式的理解處于朦朧狀態(tài),似懂非懂,直到前些天看到左盟主的博客才恍然大悟,有種相見(jiàn)恨晚的感覺(jué)。
先來(lái)簡(jiǎn)單描述一下LZ當(dāng)時(shí)項(xiàng)目中是什么樣的業(yè)務(wù)用到觀察者模式的。業(yè)務(wù)大致是這樣的,項(xiàng)目當(dāng)時(shí)牽扯到一些txt文件的上傳,文件的組要內(nèi)容是通信基站的一些狀態(tài)信息和描述,文件大多是批量上傳,上傳后要自動(dòng)解析并持久化到數(shù)據(jù)庫(kù),前臺(tái)沒(méi)有顯示的解析按鈕和請(qǐng)求,就意味著所有文件的上傳只要請(qǐng)求觸發(fā)就要通知解析功能對(duì)其進(jìn)行自動(dòng)解析。很明顯這樣的業(yè)務(wù)使用觀察者模式更合適不過(guò)了,LZ完全后知后覺(jué),先來(lái)看看觀察者模式吧!
定義
觀察者模式(有時(shí)又被稱(chēng)為發(fā)布-訂閱模式、模型-視圖模式、源-收聽(tīng)者模式或從屬者模式)是軟件設(shè)計(jì)模式的一種。在此種模式中,一個(gè)目標(biāo)物件管理所有相依于它的觀察者物件,并且在它本身的狀態(tài)改變時(shí)主動(dòng)發(fā)出通知。這通常透過(guò)呼叫各觀察者所提供的方法來(lái)實(shí)現(xiàn)。此種模式通常被用來(lái)實(shí)作事件處理系統(tǒng)。
上面的定義當(dāng)中,主要有這樣幾個(gè)意思,首先是有一個(gè)目標(biāo)的物件,通俗點(diǎn)講就是一個(gè)類(lèi),它管理了所有依賴(lài)于它的觀察者物件,或者通俗點(diǎn)說(shuō)是觀察者類(lèi),并在它自己狀態(tài)發(fā)生變化時(shí),主動(dòng)發(fā)出通知。
簡(jiǎn)單點(diǎn)概括成通俗的話來(lái)說(shuō),就是一個(gè)類(lèi)管理著所有依賴(lài)于它的觀察者類(lèi),并且它狀態(tài)變化時(shí)會(huì)主動(dòng)給這些依賴(lài)它的類(lèi)發(fā)出通知。
針對(duì)以上描述,我們先來(lái)看看百度給的類(lèi)圖
我們可以看到,被觀察類(lèi)Observable中只持有一個(gè)觀察者Observer的列表,當(dāng)被觀察者自己狀態(tài)發(fā)生改變時(shí),調(diào)用notifyObservers方法通知自己持有的這些觀察者列表中的對(duì)象,具體的觀察者Observer都是什么,被觀察者是不關(guān)心也不需要知道的。
上面就將觀察者和被觀察者二者的耦合度降到很低了,而我們具體的觀察者是必須要知道自己觀察的是誰(shuí),所以它依賴(lài)于被觀察者。
我們可以看到觀察者模式中有三個(gè)角色,一個(gè)是觀察者的抽象接口,一個(gè)是觀察者的具體實(shí)現(xiàn),一個(gè)是被觀察者,根據(jù)上邊的類(lèi)圖我們來(lái)寫(xiě)一段測(cè)試案例
(1)一個(gè)抽象的觀察者接口(用于定義觀察者發(fā)現(xiàn)被觀察者改變后所作出的相應(yīng)動(dòng)作)
/**
* 觀察者接口
*/
public interface IObserver {
void update(Observable o);
}
(2)一個(gè)被觀察者
/**
* 被觀察者類(lèi)
*/
public class Observable {
//觀察者列表
List<IObserver> observers = new ArrayList<>();
//給被觀察者添加觀察者
public void addObserver(IObserver o){
observers.add(o);
}
public void change(){
System.out.println("被觀察者已經(jīng)發(fā)送改變~~~~~");
//通知觀察者們
notifyObservers();
}
private void notifyObservers() {
for(IObserver o:observers){
o.update(this);
}
}
}
(3)若干個(gè)觀察者的具體實(shí)現(xiàn)類(lèi)
/**
* 觀察者A
*/
public class ObserverA implements IObserver {
public void update(Observable o) {
System.out.println("觀察者A觀察到" + o.getClass().getSimpleName() + "發(fā)生變化");
System.out.println("觀察者A做出相應(yīng)");
}
}
/**
* 觀察者B
*/
public class ObserverB implements IObserver {
public void update(Observable o) {
System.out.println("觀察者B觀察到" + o.getClass().getSimpleName() + "發(fā)生變化");
System.out.println("觀察者B做出相應(yīng)");
}
}
(4)測(cè)試類(lèi)
public class TestDemo {
@Test
public void fun1(){
//創(chuàng)建一個(gè)觀察者
Observable observable = new Observable();
//給觀察者添加被觀察者
observable.addObserver(new ObserverA());
observable.addObserver(new ObserverB());
//觀察者執(zhí)行改變方法
observable.change();
}
}
以上就是一個(gè)符合觀察者模式思想的簡(jiǎn)單demo,要注意的是,這里的被觀察者需要持有一個(gè)觀察者的列表,列表中填裝了觀察他的觀察者,它還需要有一個(gè)添加觀察者的方法,來(lái)吧需要觀察他的觀察者添加到觀察者列表中,最后還需要有一個(gè)通知方法notifyObservers,當(dāng)自己改變時(shí)通知那些觀察他的觀察者。
我們可以想象生活中的一個(gè)這個(gè)例子,買(mǎi)房子,買(mǎi)房者就是觀察者,房子就是被觀察者,當(dāng)房子價(jià)格變動(dòng)時(shí)通知購(gòu)房者,我們來(lái)試著寫(xiě)一寫(xiě)這個(gè)例子:
(1)首先我們定義一個(gè)購(gòu)房者的抽象接口人(觀察者接口)
/**
* 定義一個(gè)觀察者接口(人)
*/
public interface People {
//人有一個(gè)買(mǎi)房的動(dòng)作
void buy(Home home);
}
(2)定義一個(gè)房子類(lèi)(被觀察者類(lèi))
/**
* 一個(gè)房子類(lèi)(被觀察者)
*/
public class Home {
//一個(gè)裝載購(gòu)買(mǎi)者的列表(觀察者列表)
List<People> list = new ArrayList<>();
//一個(gè)添加購(gòu)房者的方法
public void addPeople(People people){
list.add(people);
}
//一個(gè)自身的價(jià)格改變行為
public void change(){
System.out.println("房子價(jià)格有變動(dòng)!");
notifyObservers();//通知那些觀察者(買(mǎi)房的人)
}
//一個(gè)通知購(gòu)房者的方法
public void notifyObservers(){
for (People people : list) {
people.buy(this);
}
}
}
(3)若干個(gè)想購(gòu)買(mǎi)房子的人(觀察者實(shí)例)
/**
* 路人甲(觀察者1)
*/
public class PeopleA implements People {
public void buy(Home home) {
System.out.println("購(gòu)房者甲觀察到" + home.getClass().getSimpleName() + "發(fā)生變化");
System.out.println("購(gòu)房者甲做出了相應(yīng)決定");
}
}
/**
* 路人乙(觀察者2)
*/
public class PeopleB implements People {
public void buy(Home home) {
System.out.println("購(gòu)房者乙觀察到" + home.getClass().getSimpleName() + "發(fā)生變化");
System.out.println("購(gòu)房者乙做出了相應(yīng)決定");
}
}
(4)測(cè)試類(lèi)
public class App {
public static void main(String[] args) {
//創(chuàng)建一個(gè)房子
Home home = new Home();
//給房子添加觀察者
home.addPeople(new PeopleA());
home.addPeople(new PeopleB());
//房子價(jià)格有所變動(dòng)
home.change();
}
}
好了,相比看了以上貼切生活的案例大家都對(duì)觀察者模式都有一個(gè)更深的了解,針對(duì)上邊的例子,大家有沒(méi)有發(fā)現(xiàn)一個(gè)問(wèn)題,我們的被觀察者是房子,通知我們價(jià)格變動(dòng)的通知者也是房子,問(wèn)題就在這里,房子本身不會(huì)說(shuō)話,如何通知我們,所以我們聯(lián)想到了銷(xiāo)售部或者房屋中介,所以這里還缺一個(gè)角色,被觀察者的管理者。以此引入java中自帶的觀察者模式
JDK為了方便開(kāi)發(fā)人員開(kāi)發(fā),已經(jīng)寫(xiě)好了現(xiàn)成兒的觀察者接口和被觀察者接口,下面我們看看有關(guān)java中提供的觀察者接口和被觀察者接口的源碼
(1)觀察者接口
//觀察者接口,每一個(gè)觀察者都必須實(shí)現(xiàn)這個(gè)接口
public interface Observer {
//這個(gè)方法是觀察者在觀察對(duì)象產(chǎn)生變化時(shí)所做的響應(yīng)動(dòng)作,從中傳入了觀察的對(duì)象和一個(gè)預(yù)留參數(shù)
void update(Observable o, Object arg);
}
(2)被觀察者接口
//被觀察者類(lèi)
public class Observable {
//這是一個(gè)改變標(biāo)識(shí),來(lái)標(biāo)記該被觀察者有沒(méi)有改變
private boolean changed = false;
//持有一個(gè)觀察者列表
private Vector obs;
public Observable() {
obs = new Vector();
}
//添加觀察者,添加時(shí)會(huì)去重
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
//刪除觀察者
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
//notifyObservers(Object arg)的重載方法
public void notifyObservers() {
notifyObservers(null);
}
//通知所有觀察者,被觀察者改變了,你可以執(zhí)行你的update方法了。
public void notifyObservers(Object arg) {
//一個(gè)臨時(shí)的數(shù)組,用于并發(fā)訪問(wèn)被觀察者時(shí),留住觀察者列表的當(dāng)前狀態(tài),這種處理方式其實(shí)也算是一種設(shè)計(jì)模式,即備忘錄模式。
Object[] arrLocal;
//注意這個(gè)同步塊,它表示在獲取觀察者列表時(shí),該對(duì)象是被鎖定的
//也就是說(shuō),在我獲取到觀察者列表之前,不允許其他線程改變觀察者列表
synchronized (this) {
//如果沒(méi)變化直接返回
if (!changed)
return;
//這里將當(dāng)前的觀察者列表放入臨時(shí)數(shù)組
arrLocal = obs.toArray();
//將改變標(biāo)識(shí)重新置回未改變
clearChanged();
}
//注意這個(gè)for循環(huán)沒(méi)有在同步塊,此時(shí)已經(jīng)釋放了被觀察者的鎖,其他線程可以改變觀察者列表
//但是這并不影響我們當(dāng)前進(jìn)行的操作,因?yàn)槲覀円呀?jīng)將觀察者列表復(fù)制到臨時(shí)數(shù)組
//在通知時(shí)我們只通知數(shù)組中的觀察者,當(dāng)前刪除和添加觀察者,都不會(huì)影響我們通知的對(duì)象
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
//刪除所有觀察者
public synchronized void deleteObservers() {
obs.removeAllElements();
}
//標(biāo)識(shí)被觀察者被改變過(guò)了
protected synchronized void setChanged() {
changed = true;
}
//標(biāo)識(shí)被觀察者沒(méi)改變
protected synchronized void clearChanged() {
changed = false;
}
//返回被觀察者是否改變
public synchronized boolean hasChanged() {
return changed;
}
//返回觀察者數(shù)量
public synchronized int countObservers() {
return obs.size();
}
}
下面我們用jdk提供的觀察者模式來(lái)修改一下上邊的購(gòu)房案例
(1)一個(gè)觀察者people(java要求觀察者實(shí)現(xiàn)Observer接口)
/**
* 一個(gè)想購(gòu)買(mǎi)房子的觀察者
*/
public class People implements Observer {
private String name;
public People(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
//關(guān)注
public void subscribe(String homeName){
HomeAgency.getInstance().getHome(homeName).addObserver(this);
}
//取消關(guān)注
public void unsubscribe(String homeName){
HomeAgency.getInstance().getHome(homeName).deleteObserver(this);
}
//當(dāng)樓盤(pán)房子價(jià)格有所變動(dòng),就去通知購(gòu)房者
public void update(Observable o, Object arg) {
if(o instanceof Home){
Home home = (Home) o;
System.out.println(name + "知道了" + home.getName() + "價(jià)格變?yōu)椋?" + home.getLastPrice() + ", 決定去買(mǎi)房子!");
}
}
}
(2)一個(gè)被觀察者Home(java要求被觀察者需要繼承Observable類(lèi))
/**
* 一個(gè)被觀察者(房子)
*/
public class Home extends Observable {
//樓盤(pán)房子的名字
private String name;
//最后價(jià)格
private Integer lastPrice;
public Home(String name) {
super();
this.name = name;
HomeAgency.getInstance().add(this);
}
public void changePrice(Integer price){
System.out.println(name + "的價(jià)格有所改變, 改變后價(jià)格為: " + price + "平方米/元");
lastPrice = price;
//標(biāo)志改變和提醒
setChanged();
notifyObservers();
}
public String getName() {
return name;
}
public Integer getLastPrice() {
return lastPrice;
}
}
(3)一個(gè)管理被觀察者的管理器(房屋中介)
/**
* 被觀察者的管理器(房屋中介)
*/
public class HomeAgency {
private Map<String,Home> homeMap = new HashMap<>();
public void add(Home home){
homeMap.put(home.getName(), home);
}
public Home getHome(String name){
return homeMap.get(name);
}
//管理器單例
private HomeAgency(){}
public static HomeAgency getInstance(){
return HomeAgencyInstance.instance;
}
private static class HomeAgencyInstance{
private static HomeAgency instance = new HomeAgency();
}
}
(4)測(cè)試類(lèi)
public class App {
public static void main(String[] args) {
//定義兩個(gè)樓盤(pán) 五個(gè)購(gòu)房者
Home home1 = new Home("萬(wàn)科-樓盤(pán)");//毛坯
Home home2 = new Home("恒大-樓盤(pán)");//精裝修
People people1 = new People("東邪");
People people2 = new People("西毒");
People people3 = new People("南帝");
People people4 = new People("北丐");
People people5 = new People("中神通");
//東南西北關(guān)注了恒大的樓盤(pán)(沒(méi)有錢(qián),只能買(mǎi)毛坯房)
people1.subscribe("萬(wàn)科-樓盤(pán)");
people2.subscribe("萬(wàn)科-樓盤(pán)");
people3.subscribe("萬(wàn)科-樓盤(pán)");
people4.subscribe("萬(wàn)科-樓盤(pán)");
//中神通比較有錢(qián) 關(guān)注了精裝修的恒大樓盤(pán)
people5.subscribe("恒大-樓盤(pán)");
home1.changePrice(9000);
home2.changePrice(13000);
//后來(lái),中神通沒(méi)錢(qián)了,他就放棄了關(guān)注恒大樓盤(pán),轉(zhuǎn)去關(guān)注萬(wàn)科樓盤(pán)
people5.unsubscribe("恒大-樓盤(pán)");
people5.subscribe("萬(wàn)科-樓盤(pán)");
//恒大樓盤(pán)再次漲價(jià)將不會(huì)通知中神通,反之萬(wàn)科樓盤(pán)價(jià)格變動(dòng)會(huì)通知中神通
home1.changePrice(9100);
home2.changePrice(150000);
}
}
運(yùn)行結(jié)果
我們使用觀察者模式的用意是為了讓房子不在關(guān)系他價(jià)格變動(dòng)都去通知誰(shuí),更重要的是他不需要關(guān)系他通知的購(gòu)房者還是其他人,他只知道這個(gè)人實(shí)現(xiàn)了觀察者接口,即我們的觀察者依賴(lài)的只是一個(gè)抽象的觀察者接口,而不關(guān)心觀察者的具體實(shí)現(xiàn)。
另外讓購(gòu)房者來(lái)選擇自己關(guān)注的樓盤(pán),這相當(dāng)于被觀察者將維護(hù)通知對(duì)象的職能轉(zhuǎn)化給了觀察者,這樣做的好處是由于一個(gè)被觀察者可能有N多觀察者,所以讓被觀察者自己維護(hù)這個(gè)列表會(huì)很艱難,這就像一個(gè)老師被許多學(xué)生認(rèn)識(shí),那么是所有的學(xué)生都記住老師的名字簡(jiǎn)單,還是讓老師記住N多學(xué)生的名字簡(jiǎn)單?答案顯而易見(jiàn),讓學(xué)生們都記住一個(gè)老師的名字是最簡(jiǎn)單的。
以上就是設(shè)計(jì)模式中的觀察者模式