观察者模式是一种行为设计模式,允许对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并自动更新。在这种模式中,发生状态改变的对象被称为“主题”(Subject),依赖它的对象被称为“观察者”(Observer)。
观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subscribe Design Pattern)。在 GoF 的《设计模式》一书中,它的定义是这样的:
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
翻译成中文就是:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。
一般情况下,被依赖的对象叫作被观察者(Observable),依赖的对象叫作观察者(Observer)。不过,在实际的项目开发中,这两种对象的称呼是比较灵活的,有各种不同的叫法,比如:Subject-Observer、Publisher-Subscriber、Producer-Consumer等等。不管怎么称呼,只要应用场景符合刚刚给出的定义,都可以看作观察者模式。
让通过一个简单的例子来实现观察者模式。假设有一个气象站(WeatherStation),需要向许多不同的显示设备(如手机App、网站、电子屏幕等)提供实时天气数据。
首先,需要创建一个Subject接口,表示主题:
public interface Subject {
/**
* 注册观察者
* @param observer
*/
void registerObserver(Observer observer);
/**
* 删除具体的观察者
* @param observer
*/
void removeObserver(Observer observer);
/**
* 一旦发生了观察的行为,就通知所有的观察者
*/
void notifyObservers();
}
接下来,创建一个Observer接口,表示观察者:
public interface Observer {
/**
* 观察的行为发生了,该方法应该被调用
* @param newTemperature 更新的温度
*/
void update(double newTemperature);
}
现在,创建一个具体的主题,如WeatherStation
,实现Subject接口:
public class WeatherStation implements Subject{
// 温度
private double temperature;
// 持有多个观察者
private final List<Observer> observerList = new ArrayList<>();
public void changeTemperature(double newTemperature) {
this.temperature = newTemperature;
notifyObservers(newTemperature);
}
@Override
public void registerObserver(Observer observer) {
observerList.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observerList.remove(observer);
}
@Override
public void notifyObservers(double newTemperature) {
for (Observer observer : observerList) {
observer.update(newTemperature);
}
}
}
最后,创建一个具体的观察者,如 AppClient
/WebClient
,实现Observer接口:
public class AppClient implements Observer{
@Override
public void update(double newTemperature) {
System.out.println("App获取最新温度:" + newTemperature);
}
}
public class WebClient implements Observer{
@Override
public void update(double newTemperature) {
System.out.println("Web获取最新温度:" + newTemperature);
}
}
现在可以创建一个WeatherStation
实例并向其注册AppClient
观察者。当WeatherStation
的数据发生变化时,AppClient
会收到通知并更新自己的显示。
public class Main {
public static void main(String[] args) {
// 定义气象站
Subject weatherStation = new WeatherStation();
// 定义观察者
Observer appClient = new AppClient();
Observer webClient = new WebClient();
// 建立监听关系
weatherStation.registerObserver(appClient);
weatherStation.registerObserver(webClient);
// 气象站更新温度
weatherStation.notifyObservers(25.4);
}
}
在这个例子中,创建了一个WeatherStation实例,并向其注册了AppClient、WebClient观察者。当WeatherStation的数据发生变化时,所有观察者都会收到通知并更新自己的显示。 这个例子展示了观察者模式的优点:
上面的小例子算是观察者模式的“模板代码”,可以反映该模式大体的设计思路。在真实的软件开发中,并不需要照搬上面的模板代码。观察者模式的实现方法各式各样,函数、类的命名等会根据业务场景的不同有很大的差别,比如 register 函数还可以叫作 attach,remove 函数还可以叫作 detach 等等。不过,万变不离其宗,设计思路都是差不多的。
以下是一些使用观察者设计模式的例子:
发布-订阅模式和观察者模式都是用于实现对象间的松耦合通信的设计模式。尽管它们具有相似之处,但它们在实现方式和使用场景上存在一些关键区别。他们在概念上有一定的相似性,都是用于实现对象间的松耦合通信。可以将发布-订阅模式看作是观察者模式的一种变体或扩展。
我分别解释一下这两种模式。
观察者模式定义了一种一对多的依赖关系,当一个对象(被观察者)的状态发生变化时,所有依赖于它的对象(观察者)都会得到通知并自动更新。在这个模式中,被观察者和观察者之间存在直接的关联关系。观察者模式主要包括两类对象:被观察者(Subject)和观察者(Observer)
发布-订阅模式(生产者和消费者)与观察者模式类似,但它们之间有一个关键区别:发布-订阅模式引入了一个第三方组件(通常称为消息代理或事件总线),该组件负责维护发布者和订阅者之间的关系。这意味着发布者和订阅者彼此不直接通信,而是通过消息代理进行通信。这种间接通信允许发布者和订阅者在运行时动态地添加或删除,从而提高了系统的灵活性和可扩展性。
Java中的发布-订阅模式示例:
public interface Subscriber {
void onEvent(Map<String, Object> eventContextMap);
}
public class AppSubscriber implements Subscriber{
@Override
public void onEvent(Map<String, Object> eventContextMap) {
System.out.println("app -> 当前的温度是: " + eventContextMap.get("temp"));
}
}
public class WebSubscriber implements Subscriber{
@Override
public void onEvent(Map<String, Object> eventContextMap) {
System.out.println("web -> 当前的温度是: " + eventContextMap.get("temp"));
}
}
// 创建消息总线
public class EventBus {
// 维护事件(对象,字符串)和订阅者的关系
private final Map<String, List<Subscriber>> subscriberMap = new HashMap<>(8);
public void registerSubscriber(String eventType, Subscriber subscriber) {
// 通过事件类型,来确定有没有已存在订阅者
subscriberMap.computeIfAbsent(eventType, v -> new ArrayList<>());
// 获取订阅者的集合
List<Subscriber> subscriberList = subscriberMap.get(eventType);
subscriberList.add(subscriber);
// 注册
subscriberMap.put(eventType, subscriberList);
}
public void removeSubscriber(String eventType, Subscriber subscriber) {
List<Subscriber> subscriberList = subscriberMap.get(eventType);
if (subscriberList != null) {
subscriberList.remove(subscriber);
}
}
public void publishEvent(String eventType, Map<String, Object> eventContextMap) {
List<Subscriber> subscriberList = subscriberMap.get(eventType);
for (Subscriber subscriber : subscriberList) {
subscriber.onEvent(eventContextMap);
}
}
}
// 使用示例:
public class WeatherStation {
private double temperature;
private EventBus eventBus;
public WeatherStation(EventBus eventBus) {
this.eventBus = eventBus;
}
public void changeTemperature(double newTemperature) {
this.temperature = newTemperature;
Map<String, Object> eventContextMap = new HashMap<>(2);
eventContextMap.put("temp", newTemperature);
eventBus.publishEvent("changeTemperature", eventContextMap);
}
public static void main(String[] args) {
// 创建订阅者
AppSubscriber appSubscriber = new AppSubscriber();
WebSubscriber webSubscriber = new WebSubscriber();
// 构建消息总线
EventBus eventBus = new EventBus();
eventBus.registerSubscriber("changeTemperature", appSubscriber);
eventBus.registerSubscriber("changeTemperature", webSubscriber);
// 创建气象站
WeatherStation weatherStation = new WeatherStation(eventBus);
weatherStation.changeTemperature(25.6);
}
}
总结一下两者的区别:
发布-订阅模式和传统的观察者模式相比,在某些方面具有优势。以下是发布-订阅模式相对于观察者模式的一些优点:
然而,发布-订阅模式也有一些缺点,例如增加了系统的复杂性,因为引入了额外的中间组件。根据具体的应用场景和需求来选择合适的设计模式是很重要的。在某些情况下,观察者模式可能更适合,而在其他情况下,发布-订阅模式可能是更好的选择。
java.util.Observable类实现了主题(Subject)的功能,而java.util.Observer接口则定义了观察者(Observer)的方法。
通过调用Observable对象的notifyObservers()方法,可以通知所有注册的Observer对象,让它们更新自己的状态。
一下是一个使用案例:假设有一个银行账户类,它的余额是可变的。当余额发生变化时,需要通知所有的观察者(比如说银行客户),以便它们更新自己的显示信息。
// 银行账户类
public class BankAccount extends Observable {
private double balance;
// 构造函数
public BankAccount(double balance) {
this.balance = balance;
}
// 存款操作
public void deposit(double amount) {
balance += amount;
setChanged(); // 表示状态已经改变
notifyObservers(); // 通知所有观察者
}
// 取款操作
public void withdraw(double amount) {
balance -= amount;
setChanged(); // 表示状态已经改变
notifyObservers(); // 通知所有观察者
}
// 获取当前余额
public double getBalance() {
return balance;
}
// 主函数
public static void main(String[] args) {
BankAccount account = new BankAccount(1000.0);
// 创建观察者
Observer observer1 = new Observer() {
@Override
public void update(Observable o, Object arg) {
System.out.println("客户1: 余额已更新为 " + ((BankAccount)o).getBalance());
}
};
Observer observer2 = new Observer() {
@Override
public void update(Observable o, Object arg) {
System.out.println("客户2: 余额已更新为 " + ((BankAccount)o).getBalance());
}
};
// 注册观察者
account.addObserver(observer1);
account.addObserver(observer2);
// 存款操作,触发观察者更新
account.deposit(100.0);
// 取款操作,触发观察者更新
account.withdraw(50.0);
}
}
这个案例中,BankAccount类继承了java.util.Observable类,表示它是一个主题(Subject)。在存款或取款操作时,它会调用setChanged()方法表示状态已经改变,并调用notifyObservers()方法通知所有观察者(Observer)。
在主函数中,创建了两个观察者(observer1和observer2),它们分别实现了Observer接口的update()方法。当观察者收到更新通知时,它们会执行自己的业务逻辑,比如更新显示信息。
这个案例演示了观察者模式在银行系统中的应用,通过观察者模式可以实现银行客户对自己账户余额的实时监控。
Guava 库中的 EventBus
类提供了一个简单的消息总线实现,可以帮助在 Java 应用程序中实现发布-订阅模式。以下是一个简单的示例,演示了如何使用 Guava 的 EventBus
来实现一个简单的消息发布和订阅功能。
首先,确保您已将 Guava 添加到项目的依赖项中。如果您使用 Maven,请在 pom.xml
文件中添加以下依赖项:
<dependency>
<groupId>com.google.guavagroupId>
<artifactId>guavaartifactId>
<version>30.1-jreversion>
dependency>
接下来,定义一个事件类,例如 MessageEvent
:
public class MessageEvent {
private String message;
public MessageEvent(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
现在,创建一个订阅者类,例如 MessageSubscriber
。在订阅者类中,定义一个方法并使用 @Subscribe
注解标记该方法,以便 EventBus
能够识别该方法作为事件处理器:
public class MessageSubscriber {
@Subscribe
public void handleMessageEvent(MessageEvent event) {
System.out.println("收到消息: " + event.getMessage());
}
}
最后,来看一个使用示例:
public class Main {
public static void main(String[] args) {
// 创建 EventBus 实例
EventBus eventBus = new EventBus();
// 创建并注册订阅者
MessageSubscriber subscriber = new MessageSubscriber();
eventBus.register(subscriber);
// 发布事件
eventBus.post(new MessageEvent("Hello, EventBus!"));
// 取消注册订阅者
eventBus.unregister(subscriber);
// 再次发布事件(此时订阅者已取消注册,将不会收到消息)
eventBus.post(new MessageEvent("Another message"));
}
}
在这个示例中,我们创建了一个 EventBus
实例,然后创建并注册了一个 MessageSubscriber
类型的订阅者。当我们使用 eventBus.post()
方法发布一个 MessageEvent
事件时,订阅者的 handleMessageEvent
方法将被调用,并输出收到的消息。
注意,如果订阅者处理事件的方法抛出异常,EventBus
默认情况下不会对异常进行处理。如果需要处理异常,可以在创建 EventBus
实例时传入一个自定义的 SubscriberExceptionHandler
观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者一些产品的设计思路,都有这种模式的影子。
不同的应用场景和需求下,这个模式也有截然不同的实现方式,之前所列举的所有的例子都是同步阻塞的实现方式,当然我们的观察者设计模式也有异步非阻塞的实现方式;有进程内的实现方式,也有跨进程的实现方式。
之前讲到的实现方式,是一种同步阻塞的实现方式。观察者和被观察者代码在同一个线程内执行,被观察者一直阻塞,直到所有的观察者代码都执行完成之后,才执行后续的代码。对照上面讲到的用户注册的例子,register() 函数依次调用执行每个观察者的 handleRegSuccess() 函数,等到都执行完成之后,才会返回结果给客户端。
如果注册接口是一个调用比较频繁的接口,对性能非常敏感,希望接口的响应时间尽可能短,那我们可以将同步阻塞的实现方式改为异步非阻塞的实现方式,以此来减少响应时间。
首先,我们需要创建一个通用的观察者接口Observer
和一个被观察者接口Observable
。
Observer.java:
public interface Observer {
void update(String message);
}
Observable.java:
public interface Observable {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(String message);
}
接下来,我们需要实现一个具体的被观察者类Subject
和一个具体的观察者类ConcreteObserver
。
Subject.java:
public class Subject implements Observable {
private List<Observer> observers;
private ExecutorService executorService;
public Subject() {
observers = new ArrayList<>();
executorService = Executors.newCachedThreadPool();
}
@Override
public void addObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(String message) {
for (Observer observer : observers) {
executorService.submit(() -> observer.update(message));
}
}
public void setMessage(String message) {
notifyObservers(message);
}
}
ConcreteObserver.java:
public class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + " received message: " + message);
}
}
最后,我们可以创建一个简单的示例来测试实现的异步非阻塞观察者模式。
Main.java:
public class Main {
public static void main(String[] args) {
Subject subject = new Subject();
ConcreteObserver observer1 = new ConcreteObserver("Observer 1");
ConcreteObserver observer2 = new ConcreteObserver("Observer 2");
ConcreteObserver observer3 = new ConcreteObserver("Observer 3");
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.addObserver(observer3);
subject.setMessage("Hello, observers!");
// 等待异步任务完成
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在这个示例中,我们使用了ExecutorService
的线程池来实现异步非阻塞的通知。每个观察者更新操作都将作为一个任务提交给线程池并异步执行。这将确保性能敏感的场景不会因为观察者的通知而阻塞。