觀察者模式
1.1觀察者模式概述
觀察者模式(Observer Pattern)又叫做發布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式。定義了一種一對多的依賴關系,一個主題對象可被多個觀察者對象同時監聽,使得每當主題對象狀態變化時,所有依賴它的對象都會得到通知並自動更新,屬於行為型設計模式。
觀察者的核心是將觀察者與被觀察者解耦,以類似消息/廣播發送的機制聯動兩者,使被觀察者的變動能通知到感興趣的觀察者們,從而做出相應的響應。
1.2觀察者模式的應用場景
觀察者模式一般會應用到App的鬧鍾設置,消息的廣播通知,郵件通知等。
1.3觀察者模式的通用寫法
/**
* 抽象主題者
*
* @author yml
* @since 2022/4/13 11:24
*/
public interface ISubject<T> {
boolean attach(IObserver<T> observer);
boolean detach(IObserver<T> observer);
void notify(T event);
}
/**
* 抽象觀察者
*
* @author yml
* @since 2022/4/13 11:21
*/
public interface IObserver<T> {
void update(T event);
}
/**
* 具體主題者
*
* @author yml
* @since 2022/4/13 14:46
*/
public class ConcreteSubject<T> implements ISubject<T> {
private List<IObserver<T>> observers = new ArrayList<IObserver<T>>();
@Override
public boolean attach(IObserver<T> observer) {
return !this.observers.contains(observer) && this.observers.add(observer);
}
@Override
public boolean detach(IObserver<T> observer) {
return this.observers.remove(observer);
}
@Override
public void notify(T event) {
for (IObserver<T> observer:this.observers) {
observer.update(event);
}
}
}
/**
* 具體觀察者
*
* @author yml
* @since 2022/4/13 14:43
*/
public class ConcreteObserver<T> implements IObserver<T> {
@Override
public void update(T event) {
System.out.println("receive event: " + event);
}
}
/**
* @Author:yml
* @Data:2022/4/13
*/
public class ClientTest {
public static void main(String[] args) {
// 被觀察者
ISubject<String> subject = new ConcreteSubject<>();
// 觀察者
IObserver<String> observer = new ConcreteObserver<>();
// 將觀察者注冊
subject.attach(observer);
// 被觀察者通知觀察者
subject.notify("hello");
}
}
繪制出觀察者uml圖:
由上圖可以看出,觀察者模式主要包含4個角色:
- 抽象主題(ISubject):指被觀察的對象。該角色是一個抽象類或接口,定義了增加、刪除、通知觀察者對象的方法。
- 具體主題(ConcreteSubject):具體被觀察者,當其內部狀態發生變化時,會通知已注冊的觀察者。
- 抽象觀察者(IObserver):定義了響應通知的更新方法。
- 具體觀察者(ConcreteObserver):當得到狀態更新通知時,會自動做出響應。
2.1觀察者模式的具體實現
2.1.1 JDK中的觀察者模式
模擬一個場景,在線教育平台,當某個學生在線上提出問題並指出對應指導老師回答,指定老師則會收到相應郵件通知
import java.util.Observable;
/**
* 被觀察者
*
* @author yml
* @since 2022/4/13 15:48
*/
public class GPer extends Observable {
private String name = "GPer 平台";
private static GPer gPer = null;
private GPer(){}
public static GPer newInstance(){
if (null == gPer){
gPer = new GPer();
}
return gPer;
}
public String getName() {
return name;
}
public void publishQuestion(Question question){
System.out.println(question.getUserName()+":"+question.getContent());
setChanged();
notifyObservers(question);
}
}
/**
* @Author:yml
* @Data:2022/4/13
*/
public class Question {
private String userName;
private String content;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
import java.util.Observable;
import java.util.Observer;
/**
* @Author:yml
* @Data:2022/4/13
*/
public class Teacher implements Observer {
private String name;
public Teacher(String name){
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
GPer gPer = (GPer) o;
Question question = (Question) arg;
System.out.println("===========================================");
System.out.println(this.name + "老師,你好 \n" + "您收到一條來自" + gPer.getName() + "\n" + "提問者:" + question.getUserName() +
"\n" + "問題描述:" + question.getContent());
}
}
/**
* @Author:yml
* @Data:2022/4/13
*/
public class ClientTest {
public static void main(String[] args) {
GPer gPer = GPer.newInstance();
Teacher tom = new Teacher("Tom");
Teacher jerry = new Teacher("Jerry");
gPer.addObserver(tom);
gPer.addObserver(jerry);
// 用戶操作
Question question = new Question();
question.setUserName("amazing");
question.setContent("如何學好JAVA");
gPer.publishQuestion(question);
}
}
2.1.2 基於Guava API實現觀察者模式
需要引入maven依賴包
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>20.0</version>
</dependency>
import com.google.common.eventbus.EventBus;
import org.example.observereg.Question;
/**
* @Author:yml
* @Data:2022/4/13
*/
public class GuavaBus {
public static final EventBus bus = new EventBus();
public static void register(GuavaEvent event){
if (event == null){
return;
}
bus.register(event);
}
public static void question(Question question){
bus.post(question);
}
}
import com.google.common.eventbus.Subscribe;
import org.example.observereg.Question;
/**
* @Author:yml
* @Data:2022/4/13
*/
public class GuavaEvent {
@Subscribe
public void subscribe(Question question){
System.out.println(question.getUserName()+":"+question.getContent());
}
}
import org.example.observereg.Question;
/**
* @Author:yml
* @Data:2022/4/13
*/
public class GuavaEventTest {
public static void main(String[] args) {
GuavaEvent guavaEvent = new GuavaEvent();
GuavaBus.register(guavaEvent);
Question question = new Question();
question.setUserName("amazing");
question.setContent("如何學好JAVA");
GuavaBus.question(question);
}
}
3.1 觀察這模式在Spring源碼中的應用
Spring中的事件機制,Spring中的ContextLoaderListener實現ServletContextListener,ServletContextListener又繼承JDK的EventListener,實現事件監聽。
3.2 觀察者模式的優缺點
- 優點:
1.觀察者和被觀察者都是松耦合(抽象耦合),符合依賴倒置原則。
2.分離了表示層(觀察者)和數據邏輯層(被觀察者者),並且建立了一套觸發機制,使得數據的變化可以響應到多個表示層。
3.實現了一對多的通信機制,支持事件注冊機制,支持興趣分發機制,當被觀察者觸發時間是,只有訂閱的觀察者可以接受通知。 - 缺點
1.如果觀察者過多,則事件通知會耗時較長。
2.事件通知為線性,可能出現阻塞。
3.觀察者和被觀察者可能存在循環依賴,可能造成循環調用,導致系統崩潰。