李斯和韓非子都是荀子的學生胡桨,李斯是師兄,韓非子是師弟瞬雹,若干年后昧谊,李斯成為秦國的上尉,致力于統(tǒng)一全國酗捌,于是安插了間諜到各個國家的重要人物的身邊進行監(jiān)視呢诬,韓非子身邊也有很多間諜,韓非子早飯吃的什么胖缤,晚上在做什么娛樂尚镰,李斯都了如指掌,我們先通過程序把這個過程展現(xiàn)一下哪廓,看看李斯是怎么監(jiān)控韓非子的狗唉,先看類圖:
程序實現(xiàn):
public interface IHanFeiZi {
// 吃早飯
void haveBreakfast();
// 娛樂
void haveFun();
}
/**
* 被觀察者
*/
public class HanFeiZi implements IHanFeiZi {
// 韓非子是否在吃飯
private boolean isHaveBreakfast = false;
// 韓非子是否在娛樂
private boolean isHaveFun = false;
@Override
public void haveBreakfast() {
System.out.println("韓非子: 開始吃飯...");
isHaveBreakfast = true;
}
@Override
public void haveFun() {
System.out.println("韓非子: 開始娛樂...");
isHaveFun = true;
}
public boolean isHaveBreakfast() {
return this.isHaveBreakfast;
}
public boolean isHaveFun() {
return this.isHaveFun;
}
public void setHaveBreakfast(boolean haveBreakfast) {
isHaveBreakfast = haveBreakfast;
}
public void setHaveFun(boolean haveFun) {
isHaveFun = haveFun;
}
}
package p16_observer_pattern.version1;
public interface ILiSi {
// 發(fā)現(xiàn)別人有動靜,自己也要行動起來
void update(String context);
}
/**
* 觀察者
*/
public class LiSi implements ILiSi {
// 首先李斯是個觀察者涡真,一旦韓非子有活動分俯,他就要向老板匯報
@Override
public void update(String context) {
System.out.println("李斯: 觀察到韓非子活動肾筐,向秦始皇匯報...");
report(context);
}
public void report(String reportContext) {
System.out.println("李斯: 報告,韓非子有活動了: " + reportContext);
}
}
package p16_observer_pattern.version1;
/**
* 監(jiān)控程序
*/
public class Watch extends Thread {
private HanFeiZi hanFeiZi;
private LiSi liSi;
private String type;
public Watch(HanFeiZi hanFeiZi, LiSi liSi, String type) {
this.hanFeiZi = hanFeiZi;
this.liSi = liSi;
this.type = type;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 監(jiān)控是否在吃早飯
if ("breakfast".equals(type)) {
// 發(fā)現(xiàn)韓非子在吃飯缸剪,就通知李斯
if (hanFeiZi.isHaveBreakfast()) {
liSi.update("韓非子在吃早飯");
// 重置狀態(tài)吗铐,繼續(xù)監(jiān)控
hanFeiZi.setHaveBreakfast(false);
}
// 監(jiān)控是否在娛樂
} else {
// 發(fā)現(xiàn)韓非子在娛樂,就通知李斯
if (hanFeiZi.isHaveFun()) {
liSi.update("韓非子在娛樂");
// 重置狀態(tài)杏节,繼續(xù)監(jiān)控
hanFeiZi.setHaveFun(false);
}
}
}
}
}
public class Client {
public static void main(String[] args) throws InterruptedException {
LiSi liSi = new LiSi();
HanFeiZi hanFeiZi = new HanFeiZi();
// 觀察早餐
Watch watchBreakfast = new Watch(hanFeiZi, liSi, "breakfast");
watchBreakfast.start();
// 觀察娛樂
Watch watchFun = new Watch(hanFeiZi, liSi, "fun");
watchFun.start();
// 韓非子吃飯
Thread.sleep(1000);
hanFeiZi.haveBreakfast();
// 韓非子娛樂
Thread.sleep(1000);
hanFeiZi.haveFun();
}
}
運行Client
程序后唬渗,控制臺打印出如下信息:
韓非子: 開始吃飯...
李斯: 觀察到韓非子活動,向秦始皇匯報...
李斯: 報告奋渔,韓非子有活動了: 韓非子在吃早飯
韓非子: 開始娛樂...
李斯: 觀察到韓非子活動镊逝,向秦始皇匯報...
李斯: 報告,韓非子有活動了: 韓非子在娛樂
以上程序的問題:
- 使用死循環(huán)
while(true)
來做監(jiān)聽嫉鲸,占用大量CPU資源 - 不是面向對象的程序
改進蹋半,首先修改類圖:
類圖非常簡單,就是在HanFeiZi
類中引用了ILiSi
這個接口充坑,看代碼變化:
public class HanFeiZi implements IHanFeiZi {
// 引用李斯接口
private ILiSi liSi = new LiSi();
@Override
public void haveBreakfast() {
System.out.println("韓非子: 開始吃飯...");
// 通知李斯
liSi.update("韓非子在吃飯");
}
@Override
public void haveFun() {
System.out.println("韓非子: 開始娛樂...");
// 通知李斯
liSi.update("韓非子在娛樂");
}
}
public class Client {
public static void main(String[] args) throws InterruptedException {
HanFeiZi hanFeiZi = new HanFeiZi();
// 韓非子吃飯
hanFeiZi.haveBreakfast();
// 韓非子娛樂
hanFeiZi.haveFun();
}
}
運行結果正確减江,效率也提升了。
代碼還是可以繼續(xù)改善捻爷,首先辈灼,韓非子并不是只有李斯在監(jiān)控,再者也榄,韓非子并非只有吃飯和娛樂兩個活動需要監(jiān)視巡莹,為了很好的拓展程序,我們修改類圖如下:
我們把接口名稱修改了一下甜紫,這樣顯得更抽象化降宅,Observable
是被觀察者,就是類似韓非子這樣的人囚霸,Observer
接口是觀察者腰根,類似李斯這樣的,同時還有其他國家的比如王斯拓型、劉斯等额嘿,在Observable
接口中有三個比較重要的方法,分別是addObserver()
增加觀察者劣挫,deleteObserver()
刪除觀察者册养,notifyObservers()
通知所有的觀察者,這是什么意思呢压固?我這里有一個信息球拦,一個對象,我可以允許有多個對象來察看,你觀察也成坎炼,我觀察也成愧膀,只要是觀察者就成,也就是說我的改變或動作執(zhí)行点弯,會通知其他的對象扇调,看程序會更明白一點:
/**
* 所有被觀察者的通用接口
*/
public interface Observable {
// 增加觀察者
void addObserver(Observer observer);
// 刪除觀察者
void deleteObserver(Observer observer);
// 通知所有觀察者
void notifyObservers(String context);
}
public class HanFeiZi implements Observable {
// 所有的觀察者
private List<Observer> observerList = new ArrayList<>();
@Override
public void addObserver(Observer observer) {
observerList.add(observer);
}
@Override
public void deleteObserver(Observer observer) {
observerList.remove(observer);
}
@Override
public void notifyObservers(String context) {
for (Observer observer : observerList) {
observer.update(context);
}
}
public void haveBreakfast() {
System.out.println("韓非子: 開始吃飯...");
notifyObservers("韓非子在吃飯");
}
public void haveFun() {
System.out.println("韓非子: 開始娛樂...");
notifyObservers("韓非子在娛樂");
}
}
/**
* 所有觀察者的通用接口
*/
public interface Observer {
void update(String context);
}
/**
* 李斯是秦國的觀察者
*/
public class LiSi implements Observer {
@Override
public void update(String context) {
System.out.println("李斯: 觀察到韓非子活動矿咕,向秦王匯報...");
report(context);
}
public void report(String reportContext) {
System.out.println("李斯: 報告秦王抢肛,韓非子有活動了: " + reportContext);
}
}
/**
* 劉斯是齊國的觀察者
*/
public class LiuSi implements Observer {
@Override
public void update(String context) {
System.out.println("劉斯: 觀察到韓非子活動,向齊王匯報...");
report(context);
}
public void report(String reportContext) {
System.out.println("劉斯: 報告齊王碳柱,韓非子有活動了: " + reportContext);
}
}
/**
* 王斯是楚國的觀察者
*/
public class WangSi implements Observer {
@Override
public void update(String context) {
System.out.println("王斯: 觀察到韓非子活動捡絮,向楚王匯報...");
report(context);
}
public void report(String reportContext) {
System.out.println("王斯: 報告楚王,韓非子有活動了: " + reportContext);
}
}
public class Client {
public static void main(String[] args) {
Observer liSi = new LiSi();
Observer wangSi = new WangSi();
Observer liuSi = new LiuSi();
HanFeiZi hanFeiZi = new HanFeiZi();
hanFeiZi.addObserver(liSi);
hanFeiZi.addObserver(wangSi);
hanFeiZi.addObserver(liuSi);
hanFeiZi.haveBreakfast();
}
}
Client
的運行結果如下:
韓非子: 開始吃飯...
李斯: 觀察到韓非子活動莲镣,向秦王匯報...
李斯: 報告秦王福稳,韓非子有活動了: 韓非子在吃飯
王斯: 觀察到韓非子活動,向楚王匯報...
王斯: 報告楚王瑞侮,韓非子有活動了: 韓非子在吃飯
劉斯: 觀察到韓非子活動的圆,向齊王匯報...
劉斯: 報告齊王,韓非子有活動了: 韓非子在吃飯
好了半火,結果也正確了越妈,也符合開閉原則了,也同時實現(xiàn)類間解耦钮糖,想再加觀察者梅掠,就繼續(xù)實現(xiàn)Observer
接口就成了,但是店归,我們還有一個優(yōu)化點:HanFeiZi
這個實現(xiàn)類中應該抽象出一個父類阎抒,父類完全實現(xiàn)接口,HanFeiZi
這個類只實現(xiàn)兩個方法haveBreakfast()
和 haveFun()
就可以了消痛。另外且叁,JDK中提供了java.util.Observable
類和 java.util.Observer
接口,也就是說我們上面寫的那個例子中的Observable
接口可以改換成java.util.Observale
類秩伞,Observer
接口可以換成java.util.Observer
接口谴古,看如下類圖:
代碼實現(xiàn)如下:
import java.util.Observable;
public class HanFeiZi extends Observable {
public void haveBreakfast() {
System.out.println("韓非子: 開始吃飯...");
// 通知所有觀察者
super.setChanged();
super.notifyObservers("韓非子在吃飯");
}
public void haveFun() {
System.out.println("韓非子: 開始娛樂...");
super.setChanged();
super.notifyObservers("韓非子在娛樂");
}
}
/* ---------------------------------------------------------------------------------------------------------*/
import java.util.Observable;
import java.util.Observer;
public class LiSi implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("李斯: 觀察到韓非子活動,向秦王匯報...");
report(arg.toString());
}
public void report(String reportContext) {
System.out.println("李斯: 報告秦王稠歉,韓非子有活動了: " + reportContext);
}
}
/* ---------------------------------------------------------------------------------------------------------*/
import java.util.Observable;
import java.util.Observer;
public class LiuSi implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("劉斯: 觀察到韓非子活動掰担,向齊王匯報...");
report(arg.toString());
}
public void report(String reportContext) {
System.out.println("劉斯: 報告齊王,韓非子有活動了: " + reportContext);
}
}
/* ---------------------------------------------------------------------------------------------------------*/
import java.util.Observable;
import java.util.Observer;
public class WangSi implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("王斯: 觀察到韓非子活動怒炸,向楚王匯報...");
report(arg.toString());
}
public void report(String reportContext) {
System.out.println("王斯: 報告楚王带饱,韓非子有活動了: " + reportContext);
}
}
/* ---------------------------------------------------------------------------------------------------------*/
import java.util.Observer;
public class Client {
public static void main(String[] args) {
Observer liSi = new LiSi();
Observer wangSi = new WangSi();
Observer liuSi = new LiuSi();
HanFeiZi hanFeiZi = new HanFeiZi();
hanFeiZi.addObserver(liSi);
hanFeiZi.addObserver(wangSi);
hanFeiZi.addObserver(liuSi);
hanFeiZi.haveBreakfast();
}
}
程序運行結果上面的運行是結果是一模一樣的,程序已經(jīng)簡約到極致了。這就是觀察者模式勺疼,通用類圖如下:
觀察者模式在什么情況下使用呢教寂?觀察者可以實現(xiàn)消息的廣播,一個消息可以觸發(fā)多個事件执庐,這是觀察者模式非常重要的功能酪耕。使用觀察者模式也有兩個重點問題要解決:
-
廣播鏈的問題。
如果你做過數(shù)據(jù)庫的觸發(fā)器轨淌,你就應該知道有一個觸發(fā)器鏈的問題迂烁,比如表 A 上寫了一個觸發(fā)器,內(nèi)容是一個字段更新后更新表 B 的一條數(shù)據(jù)递鹉,而表 B 上也有個觸發(fā)器盟步,要更新表 C,表 C 也有觸發(fā)器…
我們的觀察者模式也是一樣的問題躏结,一個觀察者可以有雙重身份却盘,既是觀察者,也是被觀察者媳拴,這沒什么問題黄橘,但是鏈一旦建立,這個邏輯就比較復雜屈溉,可維護性非常差塞关,根據(jù)經(jīng)驗建議,在一個觀察者模式中最多出現(xiàn)一個對象既是觀察者也是被觀察者语婴,也就是說消
息最多轉發(fā)一次(傳遞兩次)描孟,這還是比較好控制的; -
異步處理問題砰左。
被觀察者發(fā)生動作了匿醒,觀察者要做出回應,如果觀察者比較多缠导,而且處理時間比較長怎么辦廉羔?那就用異步唄,異步處理就要考慮線程安全和隊列的問題僻造。
本文原書:
《您的設計模式》 作者:CBF4LIFE