一缠俺、定義
定義對象間一種一對多的依賴關(guān)系,使得每當(dāng)一個對象改變狀態(tài)有滑,則所有依賴于它的對象都會得到通知并被自動更新跃闹。
二、第一個小栗子
舉個小栗子幫助理解。
天氣預(yù)報望艺,一個氣象臺檢測溫度苛秕、氣壓、濕度找默,氣象公司獲得這些數(shù)據(jù)艇劫。氣象臺是被觀察者,氣象公司是觀察者惩激。
//抽象被觀察者
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
//氣象臺
public class WeatherDataSt implements Subject {
private float temperatrue;
private float pressure;
private float humidity;
private List<Observer> observers;
public WeatherDataSt()
{
observers=new ArrayList<Observer>();
}
//注冊觀察者
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
//刪除觀察者
@Override
public void removeObserver(Observer o) {
if (observers.contains(o)){
observers.remove(o);
}
}
//更新信息通知觀察者
@Override
public void notifyObservers() {
observers.forEach(observer -> observer.update(temperatrue,pressure,humidity));
}
public void setData(float temperatrue,float pressure,float humidity){
this.temperatrue = temperatrue;
this.pressure = pressure;
this.humidity = humidity;
}
}
//抽象觀察者
public interface Observer {
public void update(float mTemperatrue,float mPressure,float mHumidity);
}
//今日天氣預(yù)報公司
public class CurrentConditions implements Observer {
private float teperatrue,pressure,humidity;
@Override
public void update(float teperatrue, float pressure, float humidity) {
this.humidity = humidity;
this.pressure = pressure;
this.teperatrue = teperatrue;
display();
}
public void display() {
System.out.println("***Today teperatrue:" + teperatrue + "***");
System.out.println("***Today pressure:" + pressure + "***");
System.out.println("***Today humidity:" + humidity + "***");
}
}
//明日天氣公司
public class ForcastConditions implements Observer{
private float temperatrue;
private float pressure;
private float humidity;
@Override
public void update(float temperatrue, float pressure, float humidity) {
// TODO Auto-generated method stub
this.temperatrue=temperatrue;
this.pressure=pressure;
this.humidity=humidity;
display();
}
public void display()
{
System.out.println("**明天溫度:"+(temperatrue+Math.random())+"**");
System.out.println("**明天氣壓:"+(pressure+10*Math.random())+"**");
System.out.println("**明天濕度:"+(humidity+Math.random())+"**");
}
}
//測試
public class WeatherTest {
public static void main(String[] args) {
Observer o1 = new CurrentConditions();
Observer o2 = new ForcastConditions();
WeatherDataSt subject = new WeatherDataSt();
subject.registerObserver(o1);
subject.registerObserver(o2);
subject.setData(20,30,40);
subject.notifyObservers();
}
}
//打拥晟贰:
***Today teperatrue:20.0***
***Today pressure:30.0***
***Today humidity:40.0***
**明天溫度:20.748446673124064**
**明天氣壓:39.210757687486826**
**明天濕度:40.14864883880736**
Subject被觀察者
定義被觀察者必須實現(xiàn)的職責(zé),它必須能夠動態(tài)增加咧欣、取消觀察者浅缸。它一般是抽象類或者是實現(xiàn)類,僅僅完成作為被觀察者必須實現(xiàn)的職責(zé):管理觀察者并通知觀察者魄咕。
Observer觀察者
觀察者接收到消息后,即進行update(更新方法)操作蚌父,接收到的信息進行處理哮兰。
三、第二個栗子
這個栗子原版出自《設(shè)計模式之禪(第二版)》苟弛,李斯和韓非子是師兄弟喝滞,韓非子在韓國,李斯在秦國膏秫,各奉君主右遭,李斯經(jīng)常觀察韓非子的日常。這樣韓非子就是被觀察者缤削,李斯是觀察者窘哈。
//抽象被觀察者
public abstract class Observable {
private List<Observer> observers = Collections.synchronizedList(new ArrayList<>());
public void addObserver(Observer observer){
this.observers.add(observer);
}
public void deleteObserver(Observer observer){
this.observers.remove(observer);
}
public void notifyObservers(String context){
observers.forEach(observer -> observer.update(context));
}
}
//韓非子
public class HanFeiZi extends Observable {
//韓非子要吃飯了
public void haveBreakfast(){
super.notifyObservers("韓非子在吃飯");
}
//韓非子在娛樂
public void haveFun(){
super.notifyObservers("韓非子在娛樂");
}
}
//抽象觀察者
public interface Observer {
void update(String context);
}
//李斯
public class Lisi implements Observer {
@Override
public void update(String context) {
System.out.println("李斯:觀察到韓國活動,開始匯報秦始皇亭敢。滚婉。。帅刀。");
this.reportToQinShiHuang(context);
System.out.println("李斯報告完畢");
}
//匯報給秦始皇
public void reportToQinShiHuang(String context){
System.out.println("李斯報告----"+context);
}
}
//測試
public class DaDian {
public static void main(String[] args) {
Observer lisi = new Lisi();
Observer zhaogao = new ZhaoGao();
System.out.println("--------------監(jiān)視韓非子---------------");
HanFeiZi hanFeiZi = new HanFeiZi();
hanFeiZi.addObserver(lisi);
hanFeiZi.addObserver(zhaogao);
hanFeiZi.haveBreakfast();
hanFeiZi.haveFun();
}
}
打尤酶埂:
--------------監(jiān)視韓非子---------------
李斯:觀察到韓國活動,開始匯報秦始皇扣溺。骇窍。。锥余。
李斯報告----韓非子在吃飯
李斯報告完畢
趙高:觀察到韓國活動腹纳,開始匯報秦始皇。。只估。志群。
趙高報告----韓非子在吃飯
趙高報告完畢
李斯:觀察到韓國活動,開始匯報秦始皇蛔钙。锌云。。吁脱。
李斯報告----韓非子在娛樂
李斯報告完畢
趙高:觀察到韓國活動桑涎,開始匯報秦始皇。兼贡。攻冷。。
趙高報告----韓非子在娛樂
趙高報告完畢
看了兩個栗子遍希,應(yīng)該了解觀察者模式是什么了吧等曼。
觀察者模式的優(yōu)點
- 觀察者和被觀察者之間是抽象耦合,如此設(shè)計凿蒜,則不管增加觀察者還是被觀察者都非常容易擴展禁谦。
- 建立一套觸發(fā)機制。
如果秦始皇不太相信李斯废封,就讓趙高也去觀察州泊,并且同時觀察韓國君的行動。
增加被觀察者:韓國君
public class HanGuoJun extends Observable {
public void sleep(){
super.notifyObservers("韓國君睡覺了");
}
}
增加觀察者:趙高
public class ZhaoGao implements Observer {
@Override
public void update(String context) {
System.out.println("趙高:觀察到韓國活動漂洋,開始匯報秦始皇遥皂。。刽漂。演训。");
this.reportToQinShiHuang(context);
System.out.println("趙高報告完畢");
}
//匯報給秦始皇
public void reportToQinShiHuang(String context){
System.out.println("趙高報告----"+context);
}
}
測試:
public class DaDian {
public static void main(String[] args) {
Observer lisi = new Lisi();
Observer zhaogao = new ZhaoGao();
System.out.println("--------------監(jiān)視韓非子---------------");
HanFeiZi hanFeiZi = new HanFeiZi();
hanFeiZi.addObserver(lisi);
hanFeiZi.addObserver(zhaogao);
hanFeiZi.haveBreakfast();
hanFeiZi.haveFun();
System.out.println("--------------監(jiān)視韓國君---------------");
HanGuoJun hanGuoJun = new HanGuoJun();
hanGuoJun.addObserver(zhaogao);
hanGuoJun.sleep();
}
}
觀察者模式的缺點
觀察者模式需要考慮一下開發(fā)效率和運行效率問題,一個被觀察者爽冕,多個觀察者仇祭,開發(fā)和調(diào)試就會比較負(fù)責(zé),而且在java中消息的通知默認(rèn)是順序執(zhí)行颈畸,一個觀察者卡殼乌奇,會影響整體的執(zhí)行效率。這種情況下眯娱,一般考慮采用異步的方式礁苗。
多級觸發(fā)的效率更是令人擔(dān)憂
使用場景
- 關(guān)聯(lián)行為場景。需要注意的是徙缴,關(guān)聯(lián)行為是可拆分的试伙,而不是“組合”關(guān)系嘁信。
- 事件多級觸發(fā)場景。
- 跨系統(tǒng)的消息交換場景疏叨,如消息隊列的處理機制潘靖。
注意事項
廣播鏈的問題
一個觀察者可以有雙重身份,既是觀察者蚤蔓,也是被觀察者卦溢,這樣廣播鏈就比較復(fù)雜了,可維護性非常差秀又,
根據(jù)經(jīng)驗建議单寂,在一個觀察者模式中最多出現(xiàn)一個對象既是觀察者也是被觀察者,也就是說消息最多轉(zhuǎn)發(fā)一次吐辙。
注意 它和責(zé)任鏈模式的最大區(qū)別就是觀察者廣播鏈在傳播的過程中消息是隨時改變的宣决,它是由相鄰的兩個節(jié)點協(xié)商的消息結(jié)構(gòu);
而責(zé)任鏈模式在消息傳遞過程中基本上保持消息不可變昏苏,如果改變也只是在原有的消息上進行修正尊沸。
異步處理問題
被觀察者發(fā)生動作了,觀察者要做出回應(yīng)捷雕,如果觀察者比較多椒丧,而且處理時間比較長怎么辦?那就應(yīng)異步唄救巷,異步處理就要考慮線程安全和隊列的問題。
四句柠、java內(nèi)置觀察者和被觀察者
java內(nèi)部為我們提供了兩個類浦译,觀察者(java.util.Observable)是個類,被觀察者(java.util.Observer)是個接口。
看下源碼:
java.util.Observable
package java.util;
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
public Observable() {
obs = new Vector<>();
}
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);
}
public void notifyObservers() {
notifyObservers(null);
}
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
public synchronized void deleteObservers() {
obs.removeAllElements();
}
protected synchronized void setChanged() {
changed = true;
}
protected synchronized void clearChanged() {
changed = false;
}
public synchronized boolean hasChanged() {
return changed;
}
public synchronized int countObservers() {
return obs.size();
}
}
java.util.Observer
package java.util;
public interface Observer {
void update(Observable o, Object arg);
}
從源碼看出java.util.Observable里的方法基本都是同步方法溯职,線程安全的精盅。
通過java內(nèi)置的設(shè)計模式實現(xiàn)第二個栗子
public class Hanfeizi extends Observable {
public void haveBreakfast(){
super.setChanged();
super.notifyObservers("韓非子在吃飯");
}
}
public class Hanguojun extends Observable {
public void sleep(){
super.setChanged();
super.notifyObservers("韓國君在睡覺");
}
}
public class Lisi implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println(o);
this.reportToQinShiHuang(o,arg);
}
//匯報給秦始皇
public void reportToQinShiHuang(Observable o,Object arg){
System.out.println("李斯報告----"+arg.toString());
}
}
public class test {
public static void main(String[] args) {
Lisi lisi = new Lisi();
Hanfeizi hanfeizi = new Hanfeizi();
hanfeizi.addObserver(lisi);
hanfeizi.haveBreakfast();
Hanguojun hanguojun = new Hanguojun();
hanguojun.addObserver(lisi);
hanguojun.sleep();
}
}
打印:
javaInDemo.Hanfeizi@4554617c
李斯報告----韓非子在吃飯
javaInDemo.Hanguojun@74a14482
李斯報告----韓國君在睡覺
項目中真實的觀察者模式
為什么說“真實”呢谜酒?因為我們剛剛講的那些事太標(biāo)準(zhǔn)的模式了叹俏,在系統(tǒng)設(shè)計中會對觀察者模式進行改造或者改裝,主要在以下2個方面:
1僻族、觀察者和被觀察者之間的消息溝通:
觀察者和被觀察者之間的消息溝通粘驰,被觀察者狀態(tài)改變會觸發(fā)觀察者的一個行為,同時會傳遞一個消息給觀察者述么,這是正確的蝌数,在實際中一般的做法是:觀察者中的update方法接受兩個參數(shù),一個是被觀察者度秘,一個是DTO(Data Transfer Object顶伞,據(jù)傳輸對象),DTO一般是一個純潔的JavaBean,由被觀察者生成唆貌,由觀察者消費滑潘。
當(dāng)然,如果考慮到遠(yuǎn)程傳輸锨咙,一般消息是以XML格式傳遞语卤。
2、觀察者響應(yīng)方式:
觀察者一個比較復(fù)雜的邏輯蓖租,它要接受被觀察者傳遞過來的信息粱侣,同時還要對他們進行邏輯處理,在一個觀察者多個被觀察者的情況下蓖宦,性能就需要提到日程上來考慮了齐婴,為什么呢?如果觀察者來不及響應(yīng)稠茂,被觀察者的執(zhí)行時間是不是也會被拉長柠偶?那現(xiàn)在的問題就是:觀察者如何快速響應(yīng)?有兩個辦法:
一是采用多線程技術(shù)睬关,甭管是被觀察者啟動線程還是觀察者啟動線程诱担,都可以明顯地提高系統(tǒng)性能,這也就是大家通常所說的異步架構(gòu)电爹。
二是緩存技術(shù)蔫仙,甭管你誰來,我已經(jīng)準(zhǔn)備了足夠的資源給你了丐箩,我保證快速響應(yīng)摇邦,這當(dāng)然也是一種比較好方案,代價就是開發(fā)難度很大屎勘,而且壓力測試要做的足夠充分施籍,這種方案也就是大家說的同步架構(gòu)。
雖然java給我提供了觀察者模式的實現(xiàn)概漱,但是實際開發(fā)中不一定能滿足丑慎,就比如Observable,這個類是具體類瓤摧,只要是實現(xiàn)觀察者就必須繼承他竿裂,倘若這個實現(xiàn)觀察者類還要繼承一個類,那就沒辦法了姻灶,java只能是單繼承铛绰,這就增加了耦合。
項目的架構(gòu)講究的是高內(nèi)聚,低耦合。
本文講解的觀察者過于淺顯,舉的栗子也不太符合實際運用陷谱。真正的理解還是要實際開發(fā)中去融匯貫通这嚣。