个人设计师为什么做网站,石家庄市官方网站,手机兼职群,栾城哪家公司做网站观察者模式 一、概述二、使用场景三、发布订阅1) 观察者模式2) 发布-订阅模式 四、源码使用1) jdk中的观察者2) Guava中的消息总线 五、进阶1) 异步非阻塞模型 一、概述
观察者模式是一种行为设计模式#xff0c;允许对象间存在一对多的依赖关系#xff0c;当一个对象的状态… 观察者模式 一、概述二、使用场景三、发布订阅1) 观察者模式2) 发布-订阅模式 四、源码使用1) jdk中的观察者2) Guava中的消息总线 五、进阶1) 异步非阻塞模型 一、概述
观察者模式是一种行为设计模式允许对象间存在一对多的依赖关系当一个对象的状态发生改变时所有依赖它的对象都会得到通知并自动更新。在这种模式中发生状态改变的对象被称为“主题”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 ListObserver observerList new ArrayList();public void changeTemperature(double newTemperature) {this.temperature newTemperature;notifyObservers(newTemperature);}Overridepublic void registerObserver(Observer observer) {observerList.add(observer);}Overridepublic void removeObserver(Observer observer) {observerList.remove(observer);}Overridepublic void notifyObservers(double newTemperature) {for (Observer observer : observerList) {observer.update(newTemperature);}}
}最后创建一个具体的观察者如 AppClient/WebClient实现Observer接口
public class AppClient implements Observer{Overridepublic void update(double newTemperature) {System.out.println(App获取最新温度 newTemperature);}
}public class WebClient implements Observer{Overridepublic 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的数据发生变化时所有观察者都会收到通知并更新自己的显示。 这个例子展示了观察者模式的优点
观察者和主题之间的解耦主题只需要知道观察者实现了Observer接口而无需了解具体的实现细节。可以动态添加和删除观察者通过调用registerObserver和removeObserver方法可以在运行时添加和删除观察者。主题和观察者之间的通信是自动的当主题的状态发生变化时观察者会自动得到通知并更新自己的状态。 观察者模式广泛应用于各种场景例如事件处理系统、数据同步和更新通知等。学习并掌握观察者模式对于成为一个优秀的Java程序员非常有帮助。
上面的小例子算是观察者模式的“模板代码”可以反映该模式大体的设计思路。在真实的软件开发中并不需要照搬上面的模板代码。观察者模式的实现方法各式各样函数、类的命名等会根据业务场景的不同有很大的差别比如 register 函数还可以叫作 attachremove 函数还可以叫作 detach 等等。不过万变不离其宗设计思路都是差不多的。
二、使用场景
以下是一些使用观察者设计模式的例子
股票行情应用股票行情应用中当股票价格发生变化时需要通知订阅了该股票的投资者。这里股票价格更新可以作为被观察者投资者可以作为观察者。当股票价格发生变化时所有订阅了该股票的投资者都会收到通知并更新自己的投资策略。网络聊天室在网络聊天室中当有新消息时需要通知所有在线的用户。聊天室服务器可以作为被观察者用户可以作为观察者。当有新消息时聊天室服务器会通知所有在线用户更新聊天记录。拍卖系统在拍卖系统中当出价发生变化时需要通知所有关注该拍品的用户。这里拍卖系统可以作为被观察者用户可以作为观察者。当出价发生变化时所有关注该拍品的用户都会收到通知并更新自己的出价策略。订阅系统在订阅系统中当有新的内容发布时需要通知所有订阅了该内容的用户。这里内容发布可以作为被观察者用户可以作为观察者。当有新内容发布时所有订阅了该内容的用户都会收到通知并获取最新内容。游戏中的事件系统在游戏中当某个事件发生时如角色升级、道具获得等可能需要通知多个游戏模块进行相应的处理。这里游戏事件可以作为被观察者游戏模块可以作为观察者。当游戏事件发生时所有关注该事件的游戏模块都会收到通知并执行相应的逻辑。运动比赛实时更新在体育比赛中实时更新比分、技术统计等信息对于球迷和分析师非常重要。在这种场景下比赛数据更新可以作为被观察者球迷和分析师可以作为观察者。当比赛数据发生变化时所有关注比赛的球迷和分析师都会收到通知并更新数据。物联网传感器系统在物联网IoT系统中有很多传感器不断地采集数据当数据发生变化时需要通知相关联的设备或系统。在这种场景下传感器可以作为被观察者关联的设备或系统可以作为观察者。当传感器数据发生变化时所有关联的设备或系统都会收到通知并执行相应的操作。电子邮件通知系统在一个任务管理系统中当任务的状态发生变化如新任务分配、任务完成等时需要通知相关的人员。这里任务状态更新可以作为被观察者相关人员可以作为观察者。当任务状态发生变化时所有关注该任务的人员都会收到通知并查看任务详情。社交网络在社交网络中用户关注其他用户以获取实时动态。当被关注的用户发布新动态时需要通知所有关注者。在这种场景下被关注的用户可以作为被观察者关注者可以作为观察者。当被关注的用户发布新动态时所有关注者都会收到通知并查看动态。
三、发布订阅
发布-订阅模式和观察者模式都是用于实现对象间的松耦合通信的设计模式。尽管它们具有相似之处但它们在实现方式和使用场景上存在一些关键区别。他们在概念上有一定的相似性都是用于实现对象间的松耦合通信。可以将发布-订阅模式看作是观察者模式的一种变体或扩展。
我分别解释一下这两种模式。
1) 观察者模式
观察者模式定义了一种一对多的依赖关系当一个对象被观察者的状态发生变化时所有依赖于它的对象观察者都会得到通知并自动更新。在这个模式中被观察者和观察者之间存在直接的关联关系。观察者模式主要包括两类对象被观察者Subject和观察者Observer
2) 发布-订阅模式
发布-订阅模式生产者和消费者与观察者模式类似但它们之间有一个关键区别发布-订阅模式引入了一个第三方组件通常称为消息代理或事件总线该组件负责维护发布者和订阅者之间的关系。这意味着发布者和订阅者彼此不直接通信而是通过消息代理进行通信。这种间接通信允许发布者和订阅者在运行时动态地添加或删除从而提高了系统的灵活性和可扩展性。
Java中的发布-订阅模式示例
public interface Subscriber {void onEvent(MapString, Object eventContextMap);
}public class AppSubscriber implements Subscriber{Overridepublic void onEvent(MapString, Object eventContextMap) {System.out.println(app - 当前的温度是: eventContextMap.get(temp));}
}public class WebSubscriber implements Subscriber{Overridepublic void onEvent(MapString, Object eventContextMap) {System.out.println(web - 当前的温度是: eventContextMap.get(temp));}
}// 创建消息总线
public class EventBus {// 维护事件对象字符串和订阅者的关系private final MapString, ListSubscriber subscriberMap new HashMap(8);public void registerSubscriber(String eventType, Subscriber subscriber) {// 通过事件类型来确定有没有已存在订阅者subscriberMap.computeIfAbsent(eventType, v - new ArrayList());// 获取订阅者的集合ListSubscriber subscriberList subscriberMap.get(eventType);subscriberList.add(subscriber);// 注册subscriberMap.put(eventType, subscriberList);}public void removeSubscriber(String eventType, Subscriber subscriber) {ListSubscriber subscriberList subscriberMap.get(eventType);if (subscriberList ! null) {subscriberList.remove(subscriber);}}public void publishEvent(String eventType, MapString, Object eventContextMap) {ListSubscriber 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;MapString, 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);}
}总结一下两者的区别
通信方式观察者模式中观察者与被观察者之间存在直接的关联关系而发布-订阅模式中发布者和订阅者通过一个第三方组件消息代理或事件总线进行通信彼此之间不存在直接关联关系。系统复杂性发布-订阅模式引入了一个额外的组件消息代理或事件总线增加了系统的复杂性但同时也提高了系统的灵活性和可扩展性。使用场景观察者模式适用于需要将状态变化通知给其他对象的情况而发布-订阅模式适用于事件驱动的系统尤其是那些需要跨越多个模块或组件进行通信的场景。 希望这个解释能帮助您理解发布-订阅模式和观察者模式之间的区别。如果您有其他问题欢迎继续提问。
发布-订阅模式和传统的观察者模式相比在某些方面具有优势。以下是发布-订阅模式相对于观察者模式的一些优点
解耦在发布-订阅模式中发布者和订阅者之间没有直接关联它们通过一个中间组件消息代理或事件总线进行通信。这种间接通信可以使发布者和订阅者在运行时动态地添加或删除从而进一步降低了它们之间的耦合度。可扩展性发布-订阅模式允许您更容易地向系统中添加新的发布者和订阅者而无需修改现有的代码。这使得系统在不同组件之间通信时具有更好的可扩展性。模块化由于发布者和订阅者之间的通信通过中间组件进行您可以将系统划分为更小、更独立的模块。这有助于提高代码的可维护性和可读性。异步通信发布-订阅模式通常支持异步消息传递这意味着发布者和订阅者可以在不同的线程或进程中运行。这有助于提高系统的并发性能和响应能力。消息过滤在发布-订阅模式中可以利用中间组件对消息进行过滤使得订阅者只接收到感兴趣的消息。这可以提高系统的性能减少不必要的通信开销。
然而发布-订阅模式也有一些缺点例如增加了系统的复杂性因为引入了额外的中间组件。根据具体的应用场景和需求来选择合适的设计模式是很重要的。在某些情况下观察者模式可能更适合而在其他情况下发布-订阅模式可能是更好的选择。
四、源码使用
1) jdk中的观察者
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() {Overridepublic void update(Observable o, Object arg) {System.out.println(客户1: 余额已更新为 ((BankAccount)o).getBalance());}};Observer observer2 new Observer() {Overridepublic 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()方法。当观察者收到更新通知时它们会执行自己的业务逻辑比如更新显示信息。
这个案例演示了观察者模式在银行系统中的应用通过观察者模式可以实现银行客户对自己账户余额的实时监控。
2) Guava中的消息总线
Guava 库中的 EventBus 类提供了一个简单的消息总线实现可以帮助在 Java 应用程序中实现发布-订阅模式。以下是一个简单的示例演示了如何使用 Guava 的 EventBus 来实现一个简单的消息发布和订阅功能。
首先确保您已将 Guava 添加到项目的依赖项中。如果您使用 Maven请在 pom.xml 文件中添加以下依赖项
dependencygroupIdcom.google.guava/groupIdartifactIdguava/artifactIdversion30.1-jre/version
/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 {Subscribepublic 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() 函数等到都执行完成之后才会返回结果给客户端。
如果注册接口是一个调用比较频繁的接口对性能非常敏感希望接口的响应时间尽可能短那我们可以将同步阻塞的实现方式改为异步非阻塞的实现方式以此来减少响应时间。
1) 异步非阻塞模型
首先我们需要创建一个通用的观察者接口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 ListObserver observers;private ExecutorService executorService;public Subject() {observers new ArrayList();executorService Executors.newCachedThreadPool();}Overridepublic void addObserver(Observer observer) {observers.add(observer);}Overridepublic void removeObserver(Observer observer) {observers.remove(observer);}Overridepublic 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;}Overridepublic 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的线程池来实现异步非阻塞的通知。每个观察者更新操作都将作为一个任务提交给线程池并异步执行。这将确保性能敏感的场景不会因为观察者的通知而阻塞。