文章收錄在 GitHub JavaKeeper ,N線網際網路開發必備技能兵器譜
在軟體系統中經常會有這樣的需求:如果一個對象的狀态發生改變,某些與它相關的對象也要随之做出相應的變化。
- 微信公衆号,如果一個使用者訂閱了某個公衆号,那麼便會收到公衆号發來的消息,那麼,公衆号就是『被觀察者』,而使用者就是『觀察者』
- 氣象站可以将每天預測到的溫度、濕度、氣壓等以公告的形式釋出給各種第三方網站,如果天氣資料有更新,要能夠實時的通知給第三方,這裡的氣象局就是『被觀察者』,第三方網站就是『觀察者』
- MVC 模式中的模型與視圖的關系也屬于觀察與被觀察
觀察者模式是使用頻率較高的設計模式之一。

觀察者模式包含觀察目标和觀察者兩類對象,一個目标可以有任意數目的與之相依賴的觀察者,一旦觀察目标的狀态發生改變,所有的觀察者都将得到通知。
定義
觀察者模式(Observer Pattern): 定義對象間一種一對多的依賴關系,使得當每一個對象改變狀态,則所有依賴于它的對象都會得到通知并自動更新。
觀察者模式是一種對象行為型模式。
觀察者模式的别名包括釋出-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。
細究的話,釋出訂閱和觀察者有些不同,可以了解成釋出訂閱模式屬于廣義上的觀察者模式。
角色
- Subject(目标):被觀察者,它是指被觀察的對象。 從類圖中可以看到,類中有一個用來存放觀察者對象的Vector 容器(之是以使用Vector而不使用List,是因為多線程操作時,Vector在是安全的,而List則是不安全的),這個Vector容器是被觀察者類的核心,另外還有三個方法:attach方法是向這個容器中添加觀察者對象;detach方法是從容器中移除觀察者對象;notify方法是依次調用觀察者對象的對應方法。這個角色可以是接口,也可以是抽象類或者具體的類,因為很多情況下會與其他的模式混用,是以使用抽象類的情況比較多。
- ConcreteSubject(具體目标):具體目标是目标類的子類,通常它包含經常發生改變的資料,當它的狀态發生改變時,向它的各個觀察者發出通知。同時它還實作了在目标類中定義的抽象業務邏輯方法(如果有的話)。如果無須擴充目标類,則具體目标類可以省略。
- Observer(觀察者):觀察者将對觀察目标的改變做出反應,觀察者一般定義為接口,該接口聲明了更新資料的方法
,是以又稱為抽象觀察者。update()
- ConcreteObserver(具體觀察者):在具體觀察者中維護一個指向具體目标對象的引用,它存儲具體觀察者的有關狀态,這些狀态需要和具體目标的狀态保持一緻;它實作了在抽象觀察者 Observer 中定義的 update()方法。通常在實作時,可以調用具體目标類的 attach() 方法将自己添加到目标類的集合中或通過 detach() 方法将自己從目标類的集合中删除。
類圖
再記錄下 UML 類圖的注意事項,這裡我的 Subject 是抽象方法,是以用斜體,抽象方法也要用斜體,具體的各種箭頭意義,我之前也總結過《設計模式前傳——學設計模式前你要知道這些》(被網上各種文章毒害過的自己,認真記錄~~~)。
執行個體
1、定義觀察者接口
interface Observer {
public void update();
}
2、定義被觀察者
abstract class Subject {
private Vector<Observer> obs = new Vector();
public void addObserver(Observer obs){
this.obs.add(obs);
}
public void delObserver(Observer obs){
this.obs.remove(obs);
}
protected void notifyObserver(){
for(Observer o: obs){
o.update();
}
}
public abstract void doSomething();
}
3、具體的被觀察者
class ConcreteSubject extends Subject {
public void doSomething(){
System.out.println("被觀察者事件發生改變");
this.notifyObserver();
}
}
4、具體的被觀察者
class ConcreteObserver1 implements Observer {
public void update() {
System.out.println("觀察者1收到資訊,并進行處理");
}
}
class ConcreteObserver2 implements Observer {
public void update() {
System.out.println("觀察者2收到資訊,并進行處理");
}
}
5、用戶端
public class Client {
public static void main(String[] args){
Subject sub = new ConcreteSubject();
sub.addObserver(new ConcreteObserver1()); //添加觀察者1
sub.addObserver(new ConcreteObserver2()); //添加觀察者2
sub.doSomething();
}
}
輸出
被觀察者事件發生改變
觀察者1收到資訊,并進行處理
觀察者2收到資訊,并進行處理
通過運作結果可以看到,我們隻調用了
Subject
的方法,但同時兩個觀察者的相關方法都被調用了。仔細看一下代碼,其實很簡單,就是在
Subject
類中關聯一下
Observer
類,并且在
doSomething()
方法中周遊一下
Observer
的
update()
方法就行了。
優缺點
優點
- 降低了目标與觀察者之間的耦合關系,兩者之間是抽象耦合關系
- 目标與觀察者之間建立了一套觸發機制
- 支援廣播通信
- 符合“開閉原則”的要求
缺點
- 目标與觀察者之間的依賴關系并沒有完全解除,而且有可能出現循環引用
- 當觀察者對象很多時,通知的釋出會花費很多時間,影響程式的效率
應用
JDK中的觀察者模式
觀察者模式在 Java 語言中的地位非常重要。在 JDK 的 java.util 包中,提供了 Observable 類以及 Observer 接口,它們構成了 JDK 對觀察者模式的支援(可以去檢視下源碼,寫的比較嚴謹)。but,在 Java9 被棄用了。
Spring 中的觀察者模式
Spring 事件驅動模型也是觀察者模式很經典的應用。就是我們常見的項目中最常見的事件監聽器。
1. Spring 中觀察者模式的四個角色
- 事件:ApplicationEvent 是所有事件對象的父類。ApplicationEvent 繼承自 jdk 的 EventObject, 所有的事件都需要繼承 ApplicationEvent, 并且通過 source 得到事件源。
Spring 也為我們提供了很多内置事件,
ContextRefreshedEvent
、
ContextStartedEvent
ContextStoppedEvent
ContextClosedEvent
RequestHandledEvent
。
- 事件監聽:ApplicationListener,也就是觀察者,繼承自 jdk 的 EventListener,該類中隻有一個方法 onApplicationEvent。當監聽的事件發生後該方法會被執行。
- 事件源:ApplicationContext,
是 Spring 中的核心容器,在事件監聽中 ApplicationContext 可以作為事件的釋出者,也就是事件源。因為 ApplicationContext 繼承自 ApplicationEventPublisher。在ApplicationContext
中定義了事件釋出的方法:ApplicationEventPublisher
publishEvent(Object event)
- 事件管理:ApplicationEventMulticaster,用于事件監聽器的注冊和事件的廣播。監聽器的注冊就是通過它來實作的,它的作用是把 Applicationcontext 釋出的 Event 廣播給它的監聽器清單。
2. coding~~
1、定義事件
public class MyEvent extends ApplicationEvent {
public MyEvent(Object source) {
super(source);
System.out.println("my Event");
}
}
2、實作事件監聽器
@Component
class MyListenerA implements ApplicationListener<MyEvent> {
public void onApplicationEvent(MyEvent AyEvent) {
System.out.println("ListenerA received");
}
}
@Component
class MyListenerB implements ApplicationListener<MyEvent> {
public void onApplicationEvent(MyEvent AyEvent) {
System.out.println("ListenerB received");
}
}
3、事件釋出者
@Component
public class MyPublisher implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
public void publishEvent(ApplicationEvent event){
System.out.println("publish event");
applicationContext.publishEvent(event);
}
}
4、測試,先用注解方式将 MyPublisher 注入 Spring
@Configuration
@ComponentScan
public class AppConfig {
@Bean(name = "myPublisher")
public MyPublisher myPublisher(){
return new MyPublisher();
}
}
public class Client {
@Test
public void main() {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyPublisher myPublisher = (MyPublisher) context.getBean("myPublisher");
myPublisher.publishEvent(new MyEvent(this));
}
}
5、輸出
my Event
publish event
ListenerA received
ListenerB received
瞎扯
設計模式真的隻是一種設計思想,不需要非得有多個觀察者才可以用觀察者模式,隻有一個觀察者,我也要用。
再舉個栗子,我是做廣告投放的嘛(廣告投放的商品檔案一般為 xml),假如我的廣告位有些空閑流量,這我得利用起來呀,是以我就從淘寶客或者拼夕夕的多多客上通過開放的 API 擷取一些,這個時候我也可以用觀察者模式,每次請求 10 萬條商品,我就生成一個新的商品檔案,這個時候我也可以用觀察者模式,擷取商品的類是被觀察者,寫商品檔案的是觀察者,當商品夠10萬條了,就通知觀察者重新寫到一個新的檔案。
大佬可能覺這麼實作有點費勁,不用設計模式也好,或者用消息隊列也好,其實都隻是一種手段,選擇适合自己業務的,開心就好。