1. 觀察者模式
1.1 什么是觀察者模式
觀察者模式用于建立一種對象與對象之間的依賴關系,當一個對象發生改變時將自動通知其他對象,其他對象會相應地作出反應。
在觀察者模式中有如下角色:
- Subject(抽象主題/被觀察者): 抽象主題角色把所有觀察者對象保存在一個集合里,每個主題可以有任意數量的觀察者,抽象主題提供一個接口,可以增加和刪除觀察者對象。
- ConcreteSubject(具體主題/具體被觀察者): 該角色將有關狀態存入具體觀察者對象,在具體主題的內部狀態發生改變時,給所有注冊過的觀察者發送通知。
- Observer(抽象觀察者): 觀察者的抽象類,定義了一個更新接口,使得在得到主題更改通知時更新自己。
- ConcreteObserver(具體觀察者): 實現抽象觀察者定義的更新接口,以便在得到主題更改通知時更新自身的狀態。在具體觀察者中維護一個指向具體目標對象的引用,存儲具體觀察者的有關狀態,這些狀態需要與具體目標保持一致。
1.2 觀察者模式實現
- 觀察者
/*** 抽象觀察者*/
public interface Observer {// update方法: 為不同觀察者的更新(響應)行為定義相同的接口,不同的觀察者對該方法有不同的實現void update();
}/*** 具體觀察者*/
public class ConcreteObserverOne implements Observer {@Overridepublic void update() {// 獲取消息通知,執行業務代碼System.out.println("ConcreteObserverOne 得到通知!");}
}/*** 具體觀察者*/
public class ConcreteObserverTwo implements Observer {@Overridepublic void update() {// 獲取消息通知,執行業務代碼System.out.println("ConcreteObserverTwo 得到通知!");}
}
- 被觀察者
/*** 抽象目標類*/
public interface Subject {void attach(Observer observer);void detach(Observer observer);void notifyObservers();
}/*** 具體目標類*/
public class ConcreteSubject implements Subject {// 定義集合,存儲所有觀察者對象private ArrayList<Observer> observers = new ArrayList<>();// 注冊方法,向觀察者集合中增加一個觀察者@Overridepublic void attach(Observer observer) {observers.add(observer);}// 注銷方法,用于從觀察者集合中刪除一個觀察者@Overridepublic void detach(Observer observer) {observers.remove(observer);}// 通知方法@Overridepublic void notifyObservers() {// 遍歷觀察者集合,調用每一個觀察者的響應方法for (Observer obs : observers) {obs.update();}}
}
- 測試類
public class Client {public static void main(String[] args) {// 創建目標類(被觀察者)ConcreteSubject subject = new ConcreteSubject();// 注冊觀察者類,可以注冊多個subject.attach(new ConcreteObserverOne());subject.attach(new ConcreteObserverTwo());// 具體主題的內部狀態發生改變時,給所有注冊過的觀察者發送通知。subject.notifyObservers();}
}
2. 發布訂閱模式與觀察者模式的區別
2.1 定義上的不同
發布訂閱模式屬于廣義上的觀察者模式。
- 發布訂閱模式是最常用的一種觀察者模式的實現,從解耦和重用角度來看,更優于典型的觀察者模式。
2.2 兩者的區別
我們來看一下觀察者模式與發布訂閱模式結構上的區別
操作流程上的區別
- 觀察者模式:數據源直接通知訂閱者發生改變。
- 發布訂閱模式:數據源告訴第三方(事件通道)發生了改變,第三方再通知訂閱者發生了改變。
3. 觀察者模式在實際開發中的應用
3.1 實際開發中的需求場景
在我們日常業務開發中,觀察者模式的一個重要作用在于實現業務的解耦。以用戶注冊的場景為例,假設在用戶注冊完成時,需要給該用戶發送郵件、發送優惠券等操作,如下圖所示:
使用觀察者模式之后
- UserService 在完成自身的用戶注冊邏輯之后,僅需要發布一個 UserRegisterEvent 事件,而無需關注其它拓展邏輯。
- 其它 Service 可以自己訂閱 UserRegisterEvent 事件,實現自定義的拓展邏輯。
3.2 Spring事件機制
Spring 基于觀察者模式,實現了自身的事件機制,由三部分組成:
- 事件
ApplicationEvent
:通過繼承它,實現自定義事件。另外,通過它的source
屬性可以獲取事件源,timestamp
屬性可以獲得發生時間。 - 事件發布者
ApplicationEventPublisher
:通過它,可以進行事件的發布。 - 事件監聽器
ApplicationListener
:通過實現它,進行指定類型的事件的監聽。
3.3 代碼實現
(1) UserRegisterEvent
- 創建
UserRegisterEvent
事件類,繼承ApplicationEvent
類,用戶注冊事件。代碼如下:
/*** 用戶注冊事件*/
public class UserRegisterEvent extends ApplicationEvent {private String username;public UserRegisterEvent(Object source) {super(source);}public UserRegisterEvent(Object source, String username) {super(source);this.username = username;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}
}
(2) UserService (事件源+事件發布)
- 創建
UserService
類,代碼如下:
/*** 事件源角色+事件發布*/
@Service
public class UserService implements ApplicationEventPublisherAware {private Logger logger = LoggerFactory.getLogger(getClass());private ApplicationEventPublisher applicationEventPublisher;@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {this.applicationEventPublisher = applicationEventPublisher;}public void register(String username){// 執行注冊邏輯logger.info("[register][執行用戶{}的注冊邏輯]", username);// 發布用戶注冊事件applicationEventPublisher.publishEvent(new UserRegisterEvent(this, username));}
}
- 實現
ApplicationEventPublisherAware
接口,從而將ApplicationEventPublisher
注入到其中。 - 在執行完注冊邏輯后,調用
ApplicationEventPublisher
的publishEvent(ApplicationEvent event)
方法,發布UserRegisterEvent
事件。
(3) 創建 EmailService
/*** 事件監聽角色*/
@Service
public class EmailService implements ApplicationListener<UserRegisterEvent> {private Logger logger = LoggerFactory.getLogger(getClass());@Overridepublic void onApplicationEvent(UserRegisterEvent event) {logger.info("[onApplicationEvent][給用戶({}) 發送郵件]", event.getUsername());}
}
- 實現
ApplicationListener
接口,通過E
泛型設置感興趣的事件。 - 實現
onApplicationEvent(E event)
方法,針對監聽的UserRegisterEvent
事件,進行自定義處理。
(4) CouponService
@Service
public class CouponService {private Logger logger = LoggerFactory.getLogger(getClass());@EventListener public void addCoupon(UserRegisterEvent event) {logger.info("[addCoupon][給用戶({}) 發放優惠劵]", event.getUsername());}
}
- 添加
@EventListener
注解,并設置監聽的事件為UserRegisterEvent
。
(5) DemoController
- 提供
/demo/register
注冊接口
@RestController
@RequestMapping("/demo")
public class DemoController {@Autowiredprivate UserService userService;@GetMapping("/register")public String register(String username) {userService.register(username);return "success";}
}
3.4 代碼測試
- 執行
DemoApplication
類,啟動項目。 - 調用
http://127.0.0.1:8080/demo/register?username=mashibing
接口,進行注冊。IDEA 控制臺打印日志如下:
// UserService 發布 UserRegisterEvent 事件
2023-04-19 16:49:40.628 INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.service.UserService : [register][執行用戶mashibing的注冊邏輯]// EmailService 監聽處理該事件
2023-04-19 16:49:40.629 INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.listener.EmailService : [onApplicationEvent][給用戶(mashibing) 發送郵件]// CouponService 監聽處理該事件
2023-04-19 16:49:40.629 INFO 9800 --- [nio-8080-exec-1] c.m.d.o.demo02.listener.CouponService : [addCoupon][給用戶(mashibing) 發放優惠劵]
4. 觀察者模式總結
1) 觀察者模式的優點
- 降低目標類和觀察者之間的耦合
- 可以實現廣播機制
2) 觀察者模式的缺點
- 通知的發送會消耗一定的時間
- 如果被觀察者有循環依賴,會導致系統的崩潰
3) 觀察者模式常見的使用場景
- 一個對象的改變,需要改變其他對象的時候
- 一個對象的改變,需要進行通知的時候