觀察者模式是極其重要的一個設計模式,也是我幾年開發程序中使用最多的設計模式,本文首先概述觀察者模式的基本概念和Demo實作,接著是觀察者模式在Java和Spring中的應用,最后是對觀察者模式的應用場景和優缺點進行總結,
一、概念理解
觀察者模式:定義物件之間的一種一對多的依賴關系,使得每當一個物件的狀態發生變化時,其相關的依賴物件都可以得到通知并被自動更新,主要用于多個不同的物件對一個物件的某個方法會做出不同的反應!
概念啥意思呢?也就是說,如果使用觀察者模式在A的業務邏輯中呼叫B的業務邏輯,即使B的業務邏輯報錯了,仍然不影響A的執行,
比如,在我最近公司開發商城系統的程序中,提交訂單成功以后要洗掉購物車中的資訊,如果我先寫訂單提交邏輯,接著寫洗掉購物車邏輯,這樣當然沒有什么問題,但是這樣程式的健壯性太差了,
應該將該業務分成兩步,一是處理訂單成功處理邏輯,二是洗掉購物車中的資訊,即使洗掉購物車報錯了,提交訂單邏輯仍然不影響,
那應該怎么做才能讓他們互不影響呢?需要在購物車物件中要有一個方法用于洗掉購物車,還要有一個物件A用于注入(add)購物車物件和通知(notify)購物車執行它的方法,
在執行時先呼叫物件A的add方法將購物車物件添加到物件A中,在訂單提交成功以后,呼叫物件A的通知notify購物車方法執行清除購物車邏輯,
在觀察者模式中,購物車就稱為觀察者,物件A就稱為目標物件,在面向介面編程原則下,觀察者模式應該包括四個角色:
1、目標介面(subject) :它是一個抽象類,也是所有目標物件的父類,它用一個串列記錄當前目標物件有哪些觀察者物件,并提供增加、洗掉觀察者物件和通知觀察者物件的方法宣告,
2、具體目標類:可以有多個不同的具體目標類,它們同時繼承Subject類,一個目標物件就是某個具體目標類的物件,一個具體目標類負責定義它自身的事務邏輯,并在狀態改變時通知它的所有觀察者物件,
3、觀察者介面(Listener) 它也是一個抽象類,是所有觀察者物件的父類;它為所有的觀察者物件都定義了一個名為update(notify)的方法,當目標物件的狀態改變時,它就是通過呼叫它的所有觀察者物件的update(notify)方法來通知它們的,
4、具體觀察者類,可以有多個不同的具體觀察者類,它們同時繼承Listener類,一個觀察者物件就是某個具體觀察者類的物件,每個具體觀察者類都要重定義Listener類中定義的update(notify)方法,在該方法中實作它自己的任務邏輯,當它被通知的時候(目標物件呼叫它的update(notify)方法)就執行自己特有的任務,在我們的例子中是購物車觀察者,當然還能有別的,如日志觀察者,
我們基于四個角色實作demo,
二、案例實作
目標介面:包括注冊、移除、通知監聽者的方法宣告,
/**
* 這是被觀察的物件
* 目標類
* @author tcy
* @Date 17-09-2022
*/
public interface SubjectAbstract<T> {
//注冊監聽者
public void registerListener(T t);
//移除監聽者
public void removeListener(T t);
//通知監聽者
public void notifyListener();
}
目標介面實作:里面需要一個listenerList陣列存盤所有的觀察者,需要定義add和remove觀察者的方法,需要給出notify方法通知所有的觀察者物件,
/**
*
* 具體目標類
* @author tcy
* @Date 17-09-2022
*/
public class SubjectImpl implements SubjectAbstract<ListenerAbstract> {
//監聽者的注冊串列
private List<ListenerAbstract> listenerList = new ArrayList<>();
@Override
public void registerListener(ListenerAbstract myListener) {
listenerList.add(myListener);
}
@Override
public void removeListener(ListenerAbstract myListener) {
listenerList.remove(myListener);
}
@Override
public void notifyListener() {
for (ListenerAbstract myListener : listenerList) {
System.out.println("收到推送事件,開始呼叫異步邏輯...");
myListener.onEvent();
}
}
}
觀察者介面:宣告回應方法
/**
*
* 觀察者-介面
* @author tcy
* @Date 17-09-2022
*/
public interface ListenerAbstract {
void onEvent();
}
觀察者介面:實作回應方法,處理清除購物車的邏輯,
/**
* 具體觀察者類 購物車
* @author tcy
* @Date 17-09-2022
*/
public class ListenerMyShopCart implements ListenerAbstract {
@Override
public void onEvent() {
//...省略購物車處理邏輯
System.out.println("洗掉購物車中的資訊...");
}
}
我們使用Client模擬提交訂單操作,
/**
* 先使用具體目標物件的registerListener方法添加具體觀察者物件,
* 然后呼叫其notify方法通知觀察者
* @author tcy
* @Date 17-09-2022
*/
public class Client {
public static void main(String[] args) {
System.out.println("訂單成功處理邏輯...");
//創建目標物件
SubjectImpl subject=new SubjectImpl();
//具體觀察者注冊入 目標物件
ListenerMyShopCart shopCart=new ListenerMyShopCart();
//向觀察者中注冊listener
subject.registerListener(shopCart);
//發布事件,通知觀察者
subject.notifyListener();
}
}
這樣就實作了訂單的處理邏輯和購物車的邏輯解耦,即使購物車邏輯報錯也不會影響訂單處理邏輯,
既然觀察者模式是很常用的模式,而且抽象觀察者和抽象目標類方法宣告都是固定的,作為高級語言Java,Java設計者干脆內置兩個介面,開發者直接實作介面就能使用觀察者模式,
三、Java中的觀察者模式
在 Java 中,通過 java.util.Observable 類和 java.util.Observer 介面定義觀察者模式,只要實作它們的子類就可以撰寫觀察者模式實體,
Observable 類是抽象目標類,它有一個 Vector 向量,用于保存所有要通知的觀察者物件,下面來介紹它最重要的 3 個方法,
void addObserver(Observer o) 方法:用于將新的觀察者物件添加到向量中,
void notifyObservers(Object arg) 方法:呼叫向量中的所有觀察者物件的 update() 方法,通知它們資料發生改變,通常越晚加入向量的觀察者越先得到通知,
void setChange() 方法:用來設定一個 boolean 型別的內部標志位,注明目標物件發生了變化,當它為真時,notifyObservers() 才會通知觀察者,
Observer 介面是抽象觀察者,它監視目標物件的變化,當目標物件發生變化時,觀察者得到通知,并呼叫 void update(Observable o,Object arg) 方法,進行相應的作業,
我們基于Java的兩個介面,改造我們的案例,
具體目標類:
/**
* 具體目標類
* @author tcy
* @Date 19-09-2022
*/
public class SubjectObservable extends Observable {
public void notifyListener() {
super.setChanged();
System.out.println("收到推送的訊息...");
super.notifyObservers(); //通知觀察者購物車事件
}
}
具體觀察者類:
/**
* 觀察者實作類
* @author tcy
* @Date 19-09-2022
*/
public class ShopCartObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("清除購物車...");
}
}
依舊是Client模擬訂單處理邏輯,
/**
* @author tcy
* @Date 19-09-2022
*/
public class Client {
public static void main(String[] args) {
System.out.println("訂單提交成功...");
SubjectObservable observable = new SubjectObservable();
Observer shopCartObserver = new ShopCartObserver(); //購物車
observable.addObserver(shopCartObserver);
observable.notifyListener();
}
}
這樣也能實作觀察者邏輯,但Java中的觀察者模式有一定的局限性,
Observable是個類,而不是一個介面,沒有實作Serializable,所以,不能序列化和它的子類,而且他是執行緒不安全的,無法保證觀察者的執行順序,在JDK9之后已經啟用了,
寫Java的恐怕沒有不用Spring的了,作為優秀的開源框架,Spring中也有觀察者模式的大量應用,而且Spring是在java的基礎之上改造的,很好的規避了Java觀察者模式的不足之處,
四、Spring如何使用觀察者模式
在第一章節典型的觀察者模式中包含著四個角色:目標類、目標類實作、觀察者、觀察者實作類,而在Spring下的觀察者模式略有不同,Spring對其做了部分改造,
事件:
Spring中定義最頂層的事件ApplicationEvent,這個介面最侄訓是繼承了EventObject介面,

只是在基礎之上增加了構造和獲取當前時間戳的方法,Spring所有的事件都要實作這個介面,比如Spring中內置的ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent...看名字大概就知道這些事件用于哪些地方,分別是容器重繪后、開始時、停止時...
目標類介面:
Spirng中的ApplicationEventMulticaster介面就是實體中目標類,我們可以對比我們的目標介面和ApplicationEventMulticaster介面,長的非常像,

觀察者介面:
觀察者ApplicationListener用于監聽事件,只有一個方法onApplicationEvent事件發生后該事件執行,與我們樣例中的抽象觀察者并無太大的不同,
目標類實作:
在我們案例中目標類的職責直接在一個類中實作,注冊監聽器、廣播事件(呼叫監聽器方法),
在Spring中兩個實作類分別拆分開來,Spring啟動程序中會呼叫registerListeners()方法,看名字我們大概就已經知道是注冊所有的監聽器,該方法完成原目標類的注冊監聽器職責,
在Spring中事件源ApplicationContext用于廣播事件,用戶不必再顯示的呼叫監聽器的方法,交給Spring呼叫,該方法完成原目標類的廣播事件職責,
我們基于Spring的觀察者模式繼續改造我們的案例,
購物車事件:
/**
* 購物車事件
* @author tcy
* @Date 19-09-2022
*/
@Component
public class EventShopCart extends ApplicationEvent {
private String orderId;
public EventShopCart(Object source, String orderId) {
super(source);
this.orderId=orderId;
}
public EventShopCart() {
super(1);
}
}
發布者(模擬Spring呼叫監聽器的方法,實際開發不需要寫):
/**
* 發布者
* @author tcy
* @Date 19-09-2022
*/
@Component
public class MyPublisher implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 發布事件
* 監聽該事件的監聽者都可以獲取訊息
*
* @param myEvent
*/
public void workEvent(EventShopCart myEvent) {
//該方法會呼叫監聽器實作的方法
applicationContext.publishEvent(myEvent);
}
}
監聽者:
/**
* 監聽者
* @author tcy
* @Date 19-09-2022
*/
@Component
public class ListenerShopCart implements ApplicationListener<EventShopCart> {
@Override
public void onApplicationEvent(EventShopCart myEvent) {
System.out.println("清除購物車成功...");
}
}
Client模擬呼叫:
/**
* @author tcy
* @Date 19-09-2022
*/
public class Client {
public static void main(String[] args) {
ApplicationContext ac =new AnnotationConfigApplicationContext("cn.sky1998.behavior.observer.spring");
System.out.println("訂單提交成功...");
MyPublisher bean = ac.getBean(MyPublisher.class);
EventShopCart myEvent = ac.getBean(EventShopCart.class);
bean.workEvent(myEvent);
}
}
通過Spring實作觀察者模式比我們手動寫簡單的多,
使用Spring實作觀察者模式時,觀察者介面、目標介面、目標實作,我們都不需要管,只負責繼承ApplicationEvent類定義我們自己的事件,并實作ApplicationListener<自定義事件>介面實作我們的觀察者,并在對應的業務中呼叫applicationContext.publishEvent(new ShopCartEvent(cmOrderItemList)),即實作了觀察者模式,
讀者可以拉取完整代碼本地學習,實作代碼均測驗通過上傳到碼云
五、總結
Spring使用觀察者模式我在很久之前就使用過,但是并不清楚為什么要這樣寫,學了觀察者模式以后,寫起來變得通透多了,
雖然觀察者模式的概念是:一對多的依賴關系,但不一定觀察者有多個才能使用,我們的例子都是使用的一個觀察者,
它很好的降低了目標與觀察者之間的耦合關系,目標與觀察者建立一套觸發機制,也讓他成為了最常見的設計模式,
設計模式的學習要成體系,推薦你看我往期發布的設計模式文章,
一、設計模式概述
二、設計模式之工廠方法和抽象工廠
三、設計模式之單例和原型
四、設計模式之建造者模式
五、設計模式之代理模式
六、設計模式之配接器模式
七、設計模式之橋接模式
八、設計模式之組合模式
九、設計模式之裝飾器模式
十、設計模式之外觀模式
十一、外觀模式之享元模式
十二、設計模式之責任鏈模式
十三、設計模式之命令模式
十四、設計模式之解釋器模式
十五、設計模式之迭代器模式
十六、設計模式之中介者模式
十七、設計模式之備忘錄模式
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/509368.html
標籤:其他
上一篇:設計模式之(12)——外觀模式
下一篇:設計模式之(12)——外觀模式
