一文搞懂設(shè)計模式—觀察者模式
觀察者模式(Observer Pattern)是一種常見的行為型設(shè)計模式,用于在對象之間建立一種一對多的依賴關(guān)系。當(dāng)一個對象的狀態(tài)發(fā)生變化時,所有依賴它的對象都將得到通知并自動更新。
一、使用場景
觀察者模式在許多應(yīng)用中都有廣泛的應(yīng)用,特別是當(dāng)存在對象之間的一對多關(guān)系,并且需要實時通知和更新時,觀察者模式非常適用。下面列舉幾個典型的使用場景:
- 消息發(fā)布/訂閱系統(tǒng):觀察者模式可以用于構(gòu)建消息發(fā)布/訂閱系統(tǒng),其中消息發(fā)布者充當(dāng)主題(被觀察者),而訂閱者則充當(dāng)觀察者。當(dāng)發(fā)布者發(fā)布新消息時,所有訂閱者都會收到通知并執(zhí)行相應(yīng)操作。
- 用戶界面組件:在圖形用戶界面 (GUI) 開發(fā)中,觀察者模式常被用于處理用戶界面組件之間的交互。當(dāng)一個組件的狀態(tài)發(fā)生變化時,其他依賴該組件的組件將自動更新以反映新的狀態(tài)。
- 股票市場監(jiān)控:在金融領(lǐng)域,觀察者模式可用于實現(xiàn)股票市場監(jiān)控系統(tǒng)。各個投資者可以作為觀察者訂閱感興趣的股票,在股票價格變動時即時收到通知。
- 事件驅(qū)動系統(tǒng):觀察者模式也常用于事件驅(qū)動系統(tǒng)中,如圖形用戶界面框架、游戲引擎等。當(dāng)特定事件發(fā)生時,觸發(fā)相應(yīng)的回調(diào)函數(shù)并通知所有注冊的觀察者。
以上僅是觀察者模式的一些典型使用場景,實際上,只要存在對象之間的依賴關(guān)系,并且需要實現(xiàn)解耦和靈活性,觀察者模式都可以考慮作為一種設(shè)計方案。
二、實現(xiàn)方式
觀察者模式包含以下幾個核心角色:
- 主題(Subject):也稱為被觀察者或可觀察者,它是具有狀態(tài)的對象,并維護著一個觀察者列表。主題提供了添加、刪除和通知觀察者的方法。
- 觀察者(Observer):觀察者是接收主題通知的對象。觀察者需要實現(xiàn)一個更新方法,當(dāng)收到主題的通知時,調(diào)用該方法進行更新操作。
- 具體主題(Concrete Subject):具體主題是主題的具體實現(xiàn)類。它維護著觀察者列表,并在狀態(tài)發(fā)生改變時通知觀察者。
- 具體觀察者(Concrete Observer):具體觀察者是觀察者的具體實現(xiàn)類。它實現(xiàn)了更新方法,定義了在收到主題通知時需要執(zhí)行的具體操作。
下面是觀察者模式的經(jīng)典實現(xiàn)方式:
(1) 定義觀察者接口:創(chuàng)建一個名為 Observer 的接口,包含一個用于接收通知的方法,例如 update()。
public interface Observer {
void update();
}
(2) 定義主題接口:創(chuàng)建一個名為 Subject 的接口,包含用于管理觀察者的方法,如 registerObserver()、removeObserver() 和 notifyObservers()。
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
(3) 實現(xiàn)具體主題:創(chuàng)建一個具體類實現(xiàn) Subject 接口,實現(xiàn)注冊、移除和通知觀察者的方法。在狀態(tài)變化時調(diào)用 notifyObservers() 方法通知所有觀察者。
import java.util.ArrayList;
import java.util.List;
public class ConcreteSubject implements Subject {
private final List<Observer> observers = new ArrayList<>();
private int state;
public void setState(int state) {
this.state = state;
notifyObservers();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
(4) 實現(xiàn)具體觀察者:創(chuàng)建一個具體類實現(xiàn) Observer 接口,實現(xiàn)接收通知并進行相應(yīng)處理的方法。
public class ConcreteObserver implements Observer {
private String name;
private Subject subject;
public ConcreteObserver(String name, Subject subject) {
this.name = name;
this.subject = subject;
}
@Override
public void update() {
System.out.println(name+" received notification");
}
}
(5) 使用觀察者模式:在實際代碼中,我們可以創(chuàng)建具體的主題和觀察者對象,并進行注冊和觸發(fā)狀態(tài)變化。
public class Main {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
ConcreteObserver observer1 = new ConcreteObserver("Observer 1", subject);
ConcreteObserver observer2 = new ConcreteObserver("Observer 2", subject);
subject.registerObserver(observer1);
subject.registerObserver(observer2);
subject.setState(10);
// Output:
// Observer 1 received notification
// Observer 2 received notification
subject.removeObserver(observer1);
subject.setState(20);
// Output:
// Observer 2 received notification
}
}
1.Java對觀察者模式的支持
觀察者模式在Java語言中的地位非常重要。在JDK的 java.util 包中,提供 Observable 類以及 Observer 接口,它們構(gòu)成了Java語言對觀察者模式的支持。
使用 Observable 類以及 Observer 接口,優(yōu)化之后的代碼為:
// 具體觀察者
public class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
// 設(shè)置每一個觀察者的名字
this.name = name;
}
/**
* 當(dāng)變化之后,就會自動觸發(fā)該方法
*/
@Override
public void update(Observable o, Object arg) {
if (arg instanceof Integer) {
System.out.println(this.name + " 觀察到 state 更改為:" + arg);
}
}
}
// 被觀察者,繼承 Observable 表示可以被觀察
public class ConcreteSubject extends Observable {
private int state;
public ConcreteSubject(int state) {
this.setState(state);
}
public int getState() {
return state;
}
public void setState(int state) {
// 設(shè)置變化點
super.setChanged();
// 狀態(tài)變化,通知觀察者
super.notifyObservers(state);
this.state = state;
}
@Override
public String toString() {
return "state:" + this.state;
}
}
public class TestObserve {
public static void main(String[] args) {
// 創(chuàng)建被觀察者
ConcreteSubject subject = new ConcreteSubject(0);
// 創(chuàng)建觀察者
ConcreteObserver ConcreteObserverA = new ConcreteObserver("觀察者 A");
ConcreteObserver ConcreteObserverB = new ConcreteObserver("觀察者 B");
ConcreteObserver ConcreteObserverC = new ConcreteObserver("觀察者 C");
// 添加可觀察對象
subject.addObserver(ConcreteObserverA);
subject.addObserver(ConcreteObserverB);
subject.addObserver(ConcreteObserverC);
System.out.println(subject);
// Output:
// state:0
subject.setState(1);
// Output:
// 觀察者 C 觀察到 state 更改為:1
// 觀察者 B 觀察到 state 更改為:1
// 觀察者 A 觀察到 state 更改為:1
System.out.println(subject);
// Output:
// state:1
}
}
2.Guava對觀察者模式的支持
Guava 中使用 Event Bus 來實現(xiàn)對觀察者模式的支持。
com.google.common.eventbus.EventBus 提供了以下主要方法:
- register(Object listener):將一個對象注冊為事件的監(jiān)聽器。
- unregister(Object listener):從事件總線中注銷一個監(jiān)聽器。
- post(Object event):發(fā)布一個事件到事件總線,以便通知所有注冊的監(jiān)聽器。
- getSubscribers(Class<?> eventClass):返回訂閱指定事件類型的所有監(jiān)聽器的集合。
這些方法提供了事件的注冊、注銷、發(fā)布和獲取監(jiān)聽器等功能,使得開發(fā)者可以方便地使用 EventBus 進行事件驅(qū)動編程。
@Getter
@AllArgsConstructor
public class MyEvent {
private String message;
}
@Slf4j
public class EventSubscriber {
@Subscribe
public void handleEvent(MyEvent event) {
String message = event.getMessage();
// Handle the event logic
log.info("Received event: " + message);
}
}
@Test
public void test() {
EventBus eventBus = new EventBus();
EventSubscriber subscriber = new EventSubscriber();
eventBus.register(subscriber);
// Publish an event
eventBus.post(new MyEvent("Hello, World!"));
// Output:
// Received event: Hello, World!
}
3.Spring對觀察者模式的支持
Spring 中可以使用 Spring Event 來實現(xiàn)觀察者模式。
在Spring Event中,有一些核心的概念和組件,包括ApplicationEvent、ApplicationListener、ApplicationContext和ApplicationEventMulticaster。
(1)ApplicationEvent(應(yīng)用事件):
- ApplicationEvent是Spring Event框架中的基礎(chǔ)類,它是所有事件類的父類。
- 通過繼承ApplicationEvent,并定義自己的事件類,可以創(chuàng)建特定類型的事件對象。
- 事件對象通常包含與事件相關(guān)的信息,例如狀態(tài)變化、操作完成等。
(2) ApplicationListener(應(yīng)用監(jiān)聽器):
- ApplicationListener是Spring Event框架中的接口,用于監(jiān)聽并處理特定類型的事件。
- 通過實現(xiàn)ApplicationListener接口,并指定感興趣的事件類型,可以創(chuàng)建具體的監(jiān)聽器。
- 監(jiān)聽器可以定義在任何Spring Bean中,當(dāng)所監(jiān)聽的事件被發(fā)布時,監(jiān)聽器會自動接收到該事件,并執(zhí)行相應(yīng)的處理邏輯。
(3) ApplicationContext(應(yīng)用上下文):
- ApplicationContext是Spring框架的核心容器,它負(fù)責(zé)管理Bean的生命周期和依賴關(guān)系。
- 在Spring Event中,ApplicationContext是事件的發(fā)布者和訂閱者的容器。
- 通過獲取ApplicationContext實例,可以獲取ApplicationEventPublisher來發(fā)布事件,也可以注冊ApplicationListener來監(jiān)聽事件。
(4) ApplicationEventMulticaster(事件廣播器):
- ApplicationEventMulticaster是Spring Event框架中的組件,用于將事件廣播給各個監(jiān)聽器。
- 它負(fù)責(zé)管理事件和監(jiān)聽器之間的關(guān)系,并將事件傳遞給對應(yīng)的監(jiān)聽器進行處理。
- Spring框架提供了幾種實現(xiàn)ApplicationEventMulticaster的類,如SimpleApplicationEventMulticaster和AsyncApplicationEventMulticaster,用于支持不同的事件分發(fā)策略。
通過使用這些關(guān)鍵概念和組件,可以在 Spring 應(yīng)用程序中實現(xiàn)事件驅(qū)動的編程模型。事件發(fā)布者(ApplicationEventPublisher)可以發(fā)布特定類型的事件,而訂閱者(ApplicationListener)可以監(jiān)聽和處理已發(fā)布的事件。ApplicationContext作為容器,負(fù)責(zé)管理事件和監(jiān)聽器,并使用ApplicationEventMulticaster來實現(xiàn)事件的廣播和分發(fā)。
下面是使用 Spring Event 實現(xiàn)觀察者模式的例子:
/**
* <p>
* 基礎(chǔ)事件發(fā)布類
* </p>
*
*/
public abstract class BaseEvent<T> extends ApplicationEvent {
/**
* 該類型事件攜帶的信息
*/
private T eventData;
/**
*
* @param source 最初觸發(fā)該事件的對象
* @param eventData 該類型事件攜帶的信息
*/
public BaseEvent(Object source, T eventData) {
super(source);
this.eventData = eventData;
}
public T getEventData() {
return eventData;
}
}
這里定義了一個基礎(chǔ)事件發(fā)布抽象類,所有的事件發(fā)布類都可以繼承此類。
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer userId;
private String userName;
}
public class UserEvent extends BaseEvent<User>{
private static final long serialVersionUID = 8145130999696021526L;
public UserEvent(Object source, User user) {
super(source,user);
}
}
@Slf4j
@Service
public class UserListener {
/*
* @Async加了就是異步監(jiān)聽,沒加就是同步(啟動類要開啟@EnableAsync注解)
* 可以使用@Order定義監(jiān)聽者順序,默認(rèn)是按代碼書寫順序
* 如果返回類型不為void,則會被當(dāng)成一個新的事件,再次發(fā)布
* @EventListener注解在EventListenerMethodProcessor類被掃描
* 可以使用SpEL表達(dá)式來設(shè)置監(jiān)聽器生效的條件
* 監(jiān)聽器可以看做普通方法,如果監(jiān)聽器拋出異常,在publishEvent里處理即可
*/
//@Async
@Order(1)
@EventListener(condition = "#userEvent.getEventData().getUserName().equals('小明')")
public String lister1(UserEvent userEvent){
User user =userEvent.getEventData();
log.info(user.toString());
return "小米";
}
@Async
@Order(2)
@EventListener
public void lister3(UserEvent userEvent){
log.info("監(jiān)聽者2");
}
@Async
@Order(3)
@EventListener
public void lister2(String name){
log.info("我叫:"+name);
}
}
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class ObserveTest {
@Resource
private ApplicationEventPublisher applicationEventPublisher;
@Test
public void test() {
applicationEventPublisher.publishEvent(new UserEvent(this, new User(1, "小明")));
// Output:
// User(userId=1, userName=小明)
// 我叫:小米
// 監(jiān)聽者2
}
}
IDEA 中可以直接跳轉(zhuǎn)到對應(yīng)的監(jiān)聽器。
相比于 Guava Event Bus,Spring Event 在實現(xiàn)觀察者模式時具有以下優(yōu)點:
- 集成性:Spring Event 是 Spring 框架的一部分,可以與其他 Spring 組件(如 Spring Boot、Spring MVC 等)無縫集成。這使得在一個應(yīng)用程序中使用 Spring Event 變得更加方便和統(tǒng)一。
- 注解驅(qū)動:Spring Event 支持使用注解來聲明事件監(jiān)聽器和發(fā)布事件。通過使用 @EventListener 注解,開發(fā)人員可以輕松定義事件監(jiān)聽器方法,并且不需要顯式注冊和注銷監(jiān)聽器。
三、優(yōu)缺點
觀察者模式有以下幾個優(yōu)點:
- 解耦性:觀察者模式能夠?qū)⒅黝}和觀察者之間的耦合度降到最低。主題與觀察者之間都是松散耦合的關(guān)系,它們之間可以獨立地進行擴展和修改,而不會相互影響。
- 靈活性:通過使用觀察者模式,可以動態(tài)地添加、刪除和通知觀察者,使系統(tǒng)更加靈活。無需修改主題或觀察者的代碼,就可以實現(xiàn)新的觀察者加入和舊觀察者離開的功能。
- 一對多關(guān)系:觀察者模式支持一對多的依賴關(guān)系,一個主題可以有多個觀察者。這樣可以方便地實現(xiàn)消息的傳遞和廣播,當(dāng)主題狀態(tài)更新時,所有觀察者都能得到通知。
雖然觀察者模式具有許多優(yōu)點,但也存在一些缺點:
- 可能引起性能問題:如果觀察者較多或通知過于頻繁,可能會導(dǎo)致性能問題。每個觀察者都需要接收通知并執(zhí)行相應(yīng)操作,當(dāng)觀察者較多時,可能會增加處理時間和系統(tǒng)負(fù)載。
- 可能引起循環(huán)依賴:由于觀察者之間可以相互注冊,如果設(shè)計不當(dāng),可能會導(dǎo)致循環(huán)依賴的問題。這樣會導(dǎo)致觸發(fā)通知的死循環(huán),造成系統(tǒng)崩潰或異常。
- 順序不確定性:在觀察者模式中,觀察者的執(zhí)行順序是不確定的。如果觀察者之間有依賴關(guān)系,可能會產(chǎn)生意外的結(jié)果。
綜上所述,觀察者模式在許多場景下都非常有用,但在使用時需要注意性能問題、循環(huán)依賴和執(zhí)行順序等方面的考慮。