在學習 JavaWeb 的監聽器的時候,深入思考了下它的實現機制,通過 Google 了解到原來這是利用了觀察者模式實現的此功能。故特地來學習了下觀察者設計模式,這種方式也叫作發布訂閱模式。
「學習啟示」:設計模式這種東西如果沒有遇到具體的應用是不會對它有什么深刻理解的,所以對于學習設計模式應該是當遇到了具體的應用實例的時候,再去學習,這時候才是最有效的。最好的方式就是學習 JDK 或者框架源碼中的設計模式,一方面能夠看到真正的應用,另一方面跟別人講起來的時候,就顯得更有水準,而不是停留在舉一個無用的例子的水平。
到底什么是觀察者模式?
舉個生活中的例子吧,每個人或多或少都訂閱了一些自己喜歡的微信公眾號,每當作者發布一條新的消息的時候,所有訂閱者都能夠接收得到,觀察者模式就是實現的這個功能,當然微信訂閱號肯定不是利用這種模式實現的,神似、神似。
再比如,去醫院看專家門診,也是提前掛號后,然后就一直在門口等待醫生通知,醫生拿著名單,一個個的叫人進來看病。這就是典型的觀察者模式,這個例子中,病人是監聽器,醫生是被監聽對象。
現在拿 JavaEE 標準類庫源碼中實際的應用舉例,比如 javax.servlet.ServletContextListener 接口就是專門設計來監聽 application 對象的,它能夠監聽 application 對象的創建和銷毀過程。awt 事件模型中也利用到了這個設計模式。更多細節請看監聽器 Listener 筆記和awt 編程
例子講到這里,其實就已經能夠發現,所謂的監聽器只是看起來好像監聽器而已,并不是它真的有主動監聽的能力,它真正的本質只是一個被動接收事件源發送過來的信息而已。如果事件源不主動把改變信息發送過來,監聽器是無法實現監聽功能的。
編碼示例
下面通過實際代碼來展示這個過程是怎么實現的,UML 類圖如下:
信號燈接口如下:
interface LightsInterface{
void addListener(LightsListener li);
void notifyListener(String c);
void setColor(String c);
}
信號燈類如下:
class Lights implements LightsInterface{
//燈的顏色
private String color;
//持久監聽器引用
private List<LightsListener> list = new ArrayList<>();
//添加監聽器
public void addListener(LightsListener li){
list.add(li);
}
//狀態改變了就通知所有監聽器
public void notifyListener(String c){
for(int i=0;i<list.size();i++){
LightsListener l = list.get(i);
l.updateSignal(c);
}
}
//改變燈的顏色
public void setColor(String c){
this.color = c;
//狀態改變了就通知監聽器(監聽器的核心)
this.notifyListener(c);
}
}
信號燈監聽器接口如下:
interface LightsListener{
void updateSignal(String c);
}
監聽信號燈顏色改變的實現類如下:
class LightsColorListener implements LightsListener{
public void updateSignal(String c){
System.out.println("LightsColorListener update " + c);
}
}
主方法測試如下:
public static void main(String[] args) {
LightsInterface light = new Lights();
light.addListener(new LightsColorListener());
light.addListener(new LightsColorListener());
light.setColor("red");
}
從以上的代碼實例可以看出,所謂觀察者模式,它的本質、核心就是被觀察者內部狀態發生了改變時,主動將這種信息通過它持有的監聽器的引用傳遞給監聽器對象。這個例子弄了接口,只是滿足面向接口編程的要求而已,實際應用中可以任意做出改變的,只要最核心的部分沒有改變就仍然是觀察者模式。
JavaWeb 中的 application 對象并咩有提供添加監聽器的方法,是因為它內部直接持有監聽器引用,這只是上述例子的一個變形而已。觀察者模式的核心就是被觀察者持有監聽器的引用,并主動將改變信息告訴監聽器對象。所以說,這種模式實現的監聽器和電視劇中看到的竊聽器還是有本質區別的,不要被誤導了,如果是開啟一個線程定時主動去查詢被觀察者的狀態信息,得到它的改變,這種才和電視劇中對于監聽器的概念是一樣的,javax.swing.timer 類可以完成此功能哦。