實(shí)例解析觀察者模式及其在Java設(shè)計(jì)模式開(kāi)發(fā)中的運(yùn)用
一、觀察者模式(Observer)的定義:
觀察者模式又稱為訂閱—發(fā)布模式,在此模式中,一個(gè)目標(biāo)對(duì)象管理所有相依于它的觀察者對(duì)象,并且在它本身的狀態(tài)改變時(shí)主動(dòng)發(fā)出通知。這通常透過(guò)呼叫各觀察者所提供的方法來(lái)實(shí)現(xiàn)。此種模式通常被用來(lái)事件處理系統(tǒng)。
1、觀察者模式的一般結(jié)構(gòu)
首先看下觀察者模式的類圖描述:
觀察者模式的角色如下:
Subject(抽象主題接口):定義了主題類中對(duì)觀察者列表的一系列操作, 包括增加,刪除, 通知等。
Concrete Subject(具體主題類):
Observer(抽象觀察者接口):定義了觀察者對(duì)主題類更新?tīng)顟B(tài)接受操作。
ConcreteObserver(具體觀察者類):實(shí)現(xiàn)觀察者接口更新主題類通知等邏輯。
從這個(gè)類圖可以看出, 主題類中維護(hù)了一個(gè)實(shí)現(xiàn)觀察者接口的類列表, 主題類通過(guò)這個(gè)列表來(lái)對(duì)觀察者進(jìn)行一系列的增刪改操作。觀察者類也可以主動(dòng)調(diào)用update方法來(lái)了解獲取主題類的狀態(tài)更新信息。
以上的類圖所描述的只是基本的觀察者模式的思想, 有很多不足。比如作為觀察者也可以主動(dòng)訂閱某類主題等。下面的例子將進(jìn)行一些改動(dòng), 以便適用具體的業(yè)務(wù)邏輯。
2、觀察者模式示例
我們構(gòu)建一個(gè)觀察者和主題類, 觀察者可以主動(dòng)訂閱主題或者取消主題。主題類統(tǒng)一被一個(gè)主題管理者所管理。下面給出類圖:
Subject:
public interface Subject { //注冊(cè)一個(gè)observer public void register(Observer observer); //移除一個(gè)observer public void remove(Observer observer); //通知所有觀察者 public void notifyObservers(); //獲取主題類要發(fā)布的消息 public String getMessage(); } ConcerteSubject: public class MySubject implements Subject { private List<Observer> observers; private boolean changed; private String message; //對(duì)象鎖, 用于同步更新觀察者列表 private final Object mutex = new Object(); public MySubject() { observers = new ArrayList<Observer>(); changed = false; } @Override public void register(Observer observer) { if (observer == null) throw new NullPointerException(); //保證不重復(fù) if (!observers.contains(observer)) observers.add(observer); } @Override public void remove(Observer observer) { observers.remove(observer); } @Override public void notifyObservers() { // temp list List<Observer> tempObservers = null; synchronized (mutex) { if (!changed) return; tempObservers = new ArrayList<>(this.observers); this.changed = false; } for(Observer obj : tempObservers) { obj.update(); } } //主題類發(fā)布新消息 public void makeChanged(String message) { System.out.println("The Subject make a change: " + message); this.message = message; this.changed = true; notifyObservers(); } @Override public String getMessage() { return this.message; } }
ConcerteSubject做出更新時(shí), 就通知列表中的所有觀察者, 并且調(diào)用觀察者update方法以實(shí)現(xiàn)接受通知后的邏輯。這里注意notifyObservers中的同步塊。在多線程的情況下, 為了避免主題類發(fā)布通知時(shí), 其他線程對(duì)觀察者列表的增刪操作, 同步塊中用一個(gè)臨時(shí)List來(lái)獲取當(dāng)前的觀察者列表。
SubjectManagement:主題類管理器
public class SubjectManagement { //一個(gè)記錄 名字——主題類 的Map private Map<String, Subject> subjectList = new HashMap<String, Subject>(); public void addSubject(String name, Subject subject) { subjectList.put(name, subject); } public void addSubject(Subject subject) { subjectList.put(subject.getClass().getName(), subject); } public Subject getSubject(String subjectName) { return subjectList.get(subjectName); } public void removeSubject(String name, Subject subject) { } public void removeSubject(Subject subject) { } //singleton private SubjectManagement() {} public static SubjectManagement getInstance() { return SubjectManagementInstance.instance; } private static class SubjectManagementInstance { static final SubjectManagement instance = new SubjectManagement(); } }
主題類管理器的作用就是在觀察者訂閱某個(gè)主題時(shí), 獲取此主題的實(shí)例對(duì)象。
Observer:
public interface Observer { public void update(); public void setSubject(Subject subject); } ConcerteObserver: public class MyObserver implements Observer { private Subject subject; // get the notify message from Concentrate Subject @Override public void update() { String message = subject.getMessage(); System.out.println("From Subject " + subject.getClass().getName() + " message: " + message); } @Override public void setSubject(Subject subject) { this.subject = subject; } // subcirbe some Subject public void subscribe(String subjectName) { SubjectManagement.getInstance().getSubject(subjectName).register(this); } // cancel subcribe public void cancelSubcribe(String subjectName) { SubjectManagement.getInstance().getSubject(subjectName).remove(this); } }
測(cè)試:我們將主題類和觀察者抽象成寫(xiě)者和讀者
public class ObserverTest { private static MySubject writer; @BeforeClass public static void setUpBeforeClass() throws Exception { writer = new MySubject(); //添加一個(gè)名為L(zhǎng)inus的作家 SubjectManagement.getInstance().addSubject("Linus",writer); } @Test public void test() { //定義幾個(gè)讀者 MyObserver reader1 = new MyObserver(); MyObserver reader2 = new MyObserver(); MyObserver reader3 = new MyObserver(); reader1.setSubject(writer); reader2.setSubject(writer); reader3.setSubject(writer); reader1.subscribe("Linus"); reader2.subscribe("Linus"); reader3.subscribe("Linus"); writer.makeChanged("I have a new Changed"); reader1.update(); } }
以上就是觀察者模式的小示例。可以看出每個(gè)主題類都要維護(hù)一個(gè)相應(yīng)的觀察者列表, 這里可以根據(jù)具體主題的抽象層次進(jìn)一步抽象, 將這種聚集放到一個(gè)抽象類中去實(shí)現(xiàn), 來(lái)共同維護(hù)一個(gè)列表, 當(dāng)然具體操作要看實(shí)際的業(yè)務(wù)邏輯。
二、Servlet中的Listener
再說(shuō)Servlet中的Listener之前, 先說(shuō)說(shuō)觀察者模式的另一種形態(tài)——事件驅(qū)動(dòng)模型。與上面提到的觀察者模式的主題角色一樣, 事件驅(qū)動(dòng)模型包括事件源, 具體事件, 監(jiān)聽(tīng)器, 具體監(jiān)聽(tīng)器。
Servlet中的Listener就是典型的事件驅(qū)動(dòng)模型。
JDK中有一套事件驅(qū)動(dòng)的類, 包括一個(gè)統(tǒng)一的監(jiān)聽(tīng)器接口和一個(gè)統(tǒng)一的事件源, 源碼如下:
/** * A tagging interface that all event listener interfaces must extend. * @since JDK1.1 */ public interface EventListener { }
這是一個(gè)標(biāo)志接口, JDK規(guī)定所有監(jiān)聽(tīng)器必須繼承這個(gè)接口。
public class EventObject implements java.io.Serializable { private static final long serialVersionUID = 5516075349620653480L; /** * The object on which the Event initially occurred. */ protected transient Object source; /** * Constructs a prototypical Event. * * @param source The object on which the Event initially occurred. * @exception IllegalArgumentException if source is null. */ public EventObject(Object source) { if (source == null) throw new IllegalArgumentException("null source"); this.source = source; } /** * The object on which the Event initially occurred. * * @return The object on which the Event initially occurred. */ public Object getSource() { return source; } /** * Returns a String representation of this EventObject. * * @return A a String representation of this EventObject. */ public String toString() { return getClass().getName() + "[source=" + source + "]"; } }
EvenObject是JDK給我們規(guī)定的一個(gè)統(tǒng)一的事件源。EvenObject類中定義了一個(gè)事件源以及獲取事件源的get方法。
下面就分析一下Servlet Listener的運(yùn)行流程。
1、Servlet Listener的組成
目前, Servlet中存在6種兩類事件的監(jiān)聽(tīng)器接口, 具體如下圖:
具體觸發(fā)情境如下表:
2、一個(gè)具體的Listener觸發(fā)過(guò)程
我們以ServletRequestAttributeListener為例, 來(lái)分析一下此處事件驅(qū)動(dòng)的流程。
首先一個(gè)Servlet中, HttpServletRequest調(diào)用setAttrilbute方法時(shí), 實(shí)際上是調(diào)用的org.apache.catalina.connector.request#setAttrilbute方法。 我們看下它的源碼:
public void setAttribute(String name, Object value) { ... //上面的邏輯代碼已省略 // 此處即通知監(jiān)聽(tīng)者 notifyAttributeAssigned(name, value, oldValue); }
下面是notifyAttributeAssigned(String name, Object value, Object oldValue)的源碼
private void notifyAttributeAssigned(String name, Object value, Object oldValue) { //從容器中獲取webAPP中定義的Listener的實(shí)例對(duì)象 Object listeners[] = context.getApplicationEventListeners(); if ((listeners == null) || (listeners.length == 0)) { return; } boolean replaced = (oldValue != null); //創(chuàng)建相關(guān)事件對(duì)象 ServletRequestAttributeEvent event = null; if (replaced) { event = new ServletRequestAttributeEvent( context.getServletContext(), getRequest(), name, oldValue); } else { event = new ServletRequestAttributeEvent( context.getServletContext(), getRequest(), name, value); } //遍歷所有監(jiān)聽(tīng)器列表, 找到對(duì)應(yīng)事件的監(jiān)聽(tīng)器 for (int i = 0; i < listeners.length; i++) { if (!(listeners[i] instanceof ServletRequestAttributeListener)) { continue; } //調(diào)用監(jiān)聽(tīng)器的方法, 實(shí)現(xiàn)監(jiān)聽(tīng)操作 ServletRequestAttributeListener listener = (ServletRequestAttributeListener) listeners[i]; try { if (replaced) { listener.attributeReplaced(event); } else { listener.attributeAdded(event); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); context.getLogger().error(sm.getString("coyoteRequest.attributeEvent"), t); // Error valve will pick this exception up and display it to user attributes.put(RequestDispatcher.ERROR_EXCEPTION, t); } } }
上面的例子很清楚的看出ServletRequestAttributeListener是如何調(diào)用的。用戶只需要實(shí)現(xiàn)監(jiān)聽(tīng)器接口就行。Servlet中的Listener幾乎涵蓋了Servlet整個(gè)生命周期中你感興趣的事件, 靈活運(yùn)用這些Listenser可以使程序更加靈活。
三、綜合示例
舉個(gè)例子,如果你看過(guò)TVB的警匪片,你就知道臥底的工作方式。一般一個(gè)警察可能有幾個(gè)臥底,潛入敵人內(nèi)部,打探消息,臥底完全靠他的領(lǐng)導(dǎo)的指示干活,領(lǐng)導(dǎo)說(shuō)幾點(diǎn)行動(dòng),他必須按照這個(gè)時(shí)間去執(zhí)行,如果行動(dòng)時(shí)間改變,他也要立馬改變自己配合行動(dòng)的時(shí)間。領(lǐng)導(dǎo)派兩個(gè)臥底去打入敵人內(nèi)部,那么領(lǐng)導(dǎo)相當(dāng)于抽象主題,而督察警官?gòu)埲@個(gè)人派了兩個(gè)臥底李四和萬(wàn)王五,張三就相當(dāng)于具體主題,臥底相當(dāng)于抽象觀察者,這兩名臥底是李四和王五就是具體觀察者,派的這個(gè)動(dòng)作相當(dāng)于觀察者在主題的登記。那么這個(gè)類圖如下:
利用javaAPI來(lái)實(shí)現(xiàn),代碼描述如下:
package observer; import java.util.List; import java.util.Observable; import java.util.Observer; /** *描述:警察張三 */ public class Police extends Observable { private String time ; public Police(List<Observer> list) { super(); for (Observer o:list) { addObserver(o); } } public void change(String time){ this.time = time; setChanged(); notifyObservers(this.time); } }
package observer; import java.util.Observable; import java.util.Observer; /** *描述:臥底A */ public class UndercoverA implements Observer { private String time; @Override public void update(Observable o, Object arg) { time = (String) arg; System.out.println("臥底A接到消息,行動(dòng)時(shí)間為:"+time); } }
package observer; import java.util.Observable; import java.util.Observer; /** *描述:臥底B */ public class UndercoverB implements Observer { private String time; @Override public void update(Observable o, Object arg) { time = (String) arg; System.out.println("臥底B接到消息,行動(dòng)時(shí)間為:"+time); } }
package observer; import java.util.ArrayList; import java.util.List; import java.util.Observer; /** *描述:測(cè)試 */ public class Client { /** * @param args */ public static void main(String[] args) { UndercoverA o1 = new UndercoverA(); UndercoverB o2 = new UndercoverB(); List<Observer> list = new ArrayList<>(); list.add(o1); list.add(o2); Police subject = new Police(list); subject.change("02:25"); System.out.println("===========由于消息敗露,行動(dòng)時(shí)間提前========="); subject.change("01:05"); } }
測(cè)試運(yùn)行結(jié)果:
臥底B接到消息,行動(dòng)時(shí)間為:02:25 臥底A接到消息,行動(dòng)時(shí)間為:02:25 ===========由于消息敗露,行動(dòng)時(shí)間提前========= 臥底B接到消息,行動(dòng)時(shí)間為:01:05 臥底A接到消息,行動(dòng)時(shí)間為:01:05
四、總結(jié)
觀察者模式定義了對(duì)象之間一對(duì)多的關(guān)系, 當(dāng)一個(gè)對(duì)象(被觀察者)的狀態(tài)改變時(shí), 依賴它的對(duì)象都會(huì)收到通知??梢詰?yīng)用到發(fā)布——訂閱, 變化——更新這種業(yè)務(wù)場(chǎng)景中。
觀察者和被觀察者之間用松耦合的方式, 被觀察者不知道觀察者的細(xì)節(jié), 只知道觀察者實(shí)現(xiàn)了接口。
事件驅(qū)動(dòng)模型更加靈活,但也是付出了系統(tǒng)的復(fù)雜性作為代價(jià)的,因?yàn)槲覀円獮槊恳粋€(gè)事件源定制一個(gè)監(jiān)聽(tīng)器以及事件,這會(huì)增加系統(tǒng)的負(fù)擔(dān)。
觀察者模式的核心是先分清角色、定位好觀察者和被觀察者、他們是多對(duì)一的關(guān)系。實(shí)現(xiàn)的關(guān)鍵是要建立觀察者和被觀察者之間的聯(lián)系、比如在被觀察者類中有個(gè)集合是用于存放觀察者的、當(dāng)被檢測(cè)的東西發(fā)生改變的時(shí)候就要通知所有觀察者。在觀察者的構(gòu)造方法中將被觀察者傳入、同時(shí)將本身注冊(cè)到被觀察者擁有的觀察者名單中、即observers這個(gè)list中。
1.觀察者模式優(yōu)點(diǎn):
(1)抽象主題只依賴于抽象觀察者
(2)觀察者模式支持廣播通信
(3)觀察者模式使信息產(chǎn)生層和響應(yīng)層分離
2.觀察者模式缺點(diǎn):
(1)如一個(gè)主題被大量觀察者注冊(cè),則通知所有觀察者會(huì)花費(fèi)較高代價(jià)
(2)如果某些觀察者的響應(yīng)方法被阻塞,整個(gè)通知過(guò)程即被阻塞,其它觀察者不能及時(shí)被通知
- Java通俗易懂系列設(shè)計(jì)模式之觀察者模式
- Java設(shè)計(jì)模式之觀察者模式原理與用法詳解
- JAVA中常用的設(shè)計(jì)模式:?jiǎn)卫J剑S模式,觀察者模式
- Java設(shè)計(jì)模式—觀察者模式詳解
- 23種設(shè)計(jì)模式(13)java觀察者模式
- Java設(shè)計(jì)模式之觀察者模式_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
- Java經(jīng)典設(shè)計(jì)模式之觀察者模式原理與用法詳解
- java設(shè)計(jì)模式之觀察者模式學(xué)習(xí)
- java設(shè)計(jì)模式之觀察者模式
- Java設(shè)計(jì)模式開(kāi)發(fā)中使用觀察者模式的實(shí)例教程
- Java設(shè)計(jì)模式之觀察者模式(Observer模式)
相關(guān)文章
Spring+MongoDB實(shí)現(xiàn)登錄注冊(cè)功能
這篇文章主要為大家詳細(xì)介紹了Spring+MongoDB實(shí)現(xiàn)登錄注冊(cè)功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07IntelliJ IDEA2023中運(yùn)行Spring Boot找不到VM options進(jìn)
這篇文章主要介紹了IntelliJ IDEA2023中運(yùn)行Spring Boot找不到VM options進(jìn)行端口的修改的問(wèn)題解決,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11Java畢業(yè)設(shè)計(jì)實(shí)戰(zhàn)之財(cái)務(wù)預(yù)算管理系統(tǒng)的實(shí)現(xiàn)
這是一個(gè)使用了java+SSM+Jsp+Mysql+Layui+Maven開(kāi)發(fā)的財(cái)務(wù)預(yù)算管理系統(tǒng),是一個(gè)畢業(yè)設(shè)計(jì)的實(shí)戰(zhàn)練習(xí),具有財(cái)務(wù)預(yù)算管理該有的所有功能,感興趣的朋友快來(lái)看看吧2022-02-02軟件開(kāi)發(fā)基礎(chǔ)之設(shè)計(jì)模式概述
這篇文章介紹了軟件開(kāi)發(fā)基礎(chǔ)之設(shè)計(jì)模式,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-09-09解決@CachePut設(shè)置的key值無(wú)法與@CacheValue的值匹配問(wèn)題
這篇文章主要介紹了解決@CachePut設(shè)置的key的值無(wú)法與@CacheValue的值匹配問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12解決springboot項(xiàng)目打成jar包后運(yùn)行時(shí)碰到的小坑
這篇文章主要介紹了解決springboot項(xiàng)目打成jar包后運(yùn)行時(shí)碰到的小坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02MyBatis批量插入數(shù)據(jù)過(guò)程解析
這篇文章主要介紹了MyBatis批量插入數(shù)據(jù)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08一文帶你掌握J(rèn)ava?SPI的原理和實(shí)踐
在Java中,我們經(jīng)常會(huì)提到面向接口編程,這樣減少了模塊之間的耦合,更加靈活,Java?SPI?(Service?Provider?Interface)就提供了這樣的機(jī)制,本文就來(lái)講講它的原理與具體使用吧2023-05-05