我们可能需要在某个对象的状态发生变化时,自动通知其他相关的对象更新自己的状态,但是我们又不希望直接在对象中硬编码这种通知机制,因为这样会导致代码的可维护性和可扩展性变差。
为了解决这种问题,我们可以使用观察者模式。观察者模式定义了一种松耦合的对象通信机制,使得多个对象可以在一起工作,而又不必互相了解。在观察者模式中,一个对象(称为主题)维护一组依赖于它的对象(称为观察者),并在状态发生变化时自动通知所有的观察者。这种机制可以使得代码更加灵活、可扩展和易于维护,从而提高软件的质量和效率。
因此,观察者模式被广泛应用于许多领域,包括桌面应用程序、Web 应用程序、移动应用程序等等。通过使用观察者模式,我们可以在不改变对象原有行为的情况下,动态地添加新的行为,从而使得代码更加灵活和易于维护。
什么是观察者模式
官方定义:
观察者模式:定义了一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都会得到通知并自动更新。
The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
观察者模式的角色
- Subject(主题):被观察的对象,维护一个观察者列表,并提供注册、删除和通知观察者的方法。
- Observer(观察者):观察主题的对象,包含一个更新自己状态的方法。
- ConcreteSubject(具体主题):实现主题接口,维护自己的状态并在状态发生变化时通知观察者。
- ConcreteObserver(具体观察者):实现观察者接口,包含更新自己状态的具体实现。
观察者模式的实现
案例
想象一下,在一个城市里,有一个天气台和多个手机App、桌面App和网站,它们都需要显示当前的天气情况。在这种情况下,我们可以使用观察者模式来实现这个功能。 天气台(Subject)维护了当前的天气状况,并提供了一个方法来更新天气数据。手机App、桌面App和网站(Observers)订阅了天气台的天气数据,当天气数据发生变化时,天气台会自动通知所有的观察者,并更新它们自己的状态。
Subject(主题)
// 主题接口interface Subject { void registerObserver(Observer o); void removeObserver(Observer o); void notifyObservers();}复制代码
Subject(主题)类的方法比较固定,通常会包含以下三个方法:
- registerObserver(Observer o):用于注册一个Observer对象,使得Subject能够向该Observer发送通知。
- removeObserver(Observer o):用于移除一个已经注册的Observer对象。
- notifyObservers():用于向所有已注册的Observer发送通知。
Observer(观察者)
// 观察者接口interface Observer { void update(double temperature, double humidity, double pressure);}复制代码
- Observer(观察者)一般包含一个方法,即update()方法,该方法用于接收Subject发来的通知,并更新自己的状态。
ConcreteSubject(具体主题)
// 天气台类class WeatherStation implements Subject { private double temperature; private double humidity; private double pressure; private List<Observer> observers = new ArrayList<>(); public void setWeather(double temperature, double humidity, double pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; notifyObservers(); } @Override public void registerObserver(Observer o) { observers.add(o); } @Override public void removeObserver(Observer o) { observers.remove(o); } @Override public void notifyObservers() { for (Observer o : observers) { o.update(temperature, humidity, pressure); } }}复制代码
ConcreteSubject(具体主题)通常会维护一个Observer列表,用于存储所有已经注册的Observer对象,以便在状态发生变化时能够通知它们。在具体实现中,
ConcreteSubject需要实现注册Observer、移除Observer和通知Observer等方法,并在状态发生变化时调用通知方法通知所有的Observer更新自己的状态。
WeatherStation就是一个具体的主题,它维护了一个List集合,用于存储所有已经注册的Observer对象,同时提供了注册Observer、移除Observer和通知Observer等方法。这种实现方式使得我们能够动态地添加或删除Observer对象,从而实现更好的灵活性和扩展性。
ConcreteObserver(具体观察者)
// 手机App、桌面App和网站类class WeatherApp implements Observer { private String name; public WeatherApp(String name) { this.name = name; } @Override public void update(double temperature, double humidity, double pressure) { System.out.printf("%s: 当前天气温度为 %.2f ℃,湿度为 %.2f%%,气压为 %.2f kPa\n", name, temperature, humidity, pressure); }}复制代码
WeatherApp类实现了Observer接口,并重写了其中的update()方法。当WeatherStation的天气数据发生变化时,它会自动通知所有已经注册的Observer对象,并调用它们的update()方法。在WeatherApp的update()方法中,它会获取WeatherStation的当前天气数据,并更新自己的界面显示。
WeatherApp类扮演的角色是具体的Observer,它实现了Observer接口,并在其中定义了具体的更新逻辑,即根据WeatherStation的天气数据更新自己的界面显示。同时,WeatherApp类还可以动态地注册、移除和更新自己的状态,以便更好地适应不同的应用场景和需求。
客户端
// 测试代码public class ObserverPatternExample { public static void main(String[] args) { WeatherStation weatherStation = new WeatherStation(); WeatherApp app1 = new WeatherApp("手机App1"); WeatherApp app2 = new WeatherApp("桌面App1"); WeatherApp app3 = new WeatherApp("网站1"); weatherStation.registerObserver(app1); weatherStation.registerObserver(app2); weatherStation.registerObserver(app3); weatherStation.setWeather(20.5, 60.3, 101.3); }}复制代码
观察者模式类图
维基百科
在spring源码中的应用
在Spring框架中,ApplicationContext中的事件、Bean生命周期事件和自定义事件都是观察者模式的具体应用。ApplicationContext作为主题(Subject)维护了一个监听器列表(Observer),用于存储所有已经注册的监听器。当事件发生时,ApplicationContext会自动通知所有的监听器,并调用它们的事件处理方法(onApplicationEvent方法),以便执行具体的逻辑。这种模式可以帮助我们在Spring应用程序中实现对象之间的动态通信,以便更好地满足业务需求和用户需求。
- ApplicationContext中的事件,例如ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent等;
- Bean生命周期事件,例如BeforeInitializationEvent、AfterInitializationEvent等;
- 自定义事件,例如订单创建事件、支付成功事件、邮件发送成功事件等。
这三个场景都是事件驱动模型,事件驱动模型是一种实现观察者模式的方式,它将观察者模式中的主题(Subject)和观察者(Observer)抽象为事件(Event)和监听器(Listener),并使用事件和监听器之间的关系来实现观察者模式。
代码举例
我来详细说一下自定义事件
举一个订单创建事件的例子。假设我们正在开发一个电商平台,我们需要在订单创建成功时,发送一封确认邮件给用户。为了实现这个功能,我们可以定义一个OrderCreatedEvent事件,并在订单创建时发布这个事件。具体代码如下:
首先,定义一个OrderCreatedEvent类,继承自ApplicationEvent类,用于表示订单创建事件:
public class OrderCreatedEvent extends ApplicationEvent { private Order order; public OrderCreatedEvent(Object source, Order order) { super(source); this.order = order; } public Order getOrder() { return order; }}复制代码
在订单创建成功时,我们可以通过ApplicationContext对象来发布一个OrderCreatedEvent事件,示例代码如下:
@Componentpublic class OrderService { @Autowired private ApplicationContext applicationContext; public void createOrder(Order order) { // 创建订单逻辑 ... // 发布订单创建事件 applicationContext.publishEvent(new OrderCreatedEvent(this, order)); }}复制代码
上述代码中,我们首先通过@Autowired注解注入了ApplicationContext对象,然后在订单创建成功后,通过applicationContext.publishEvent()方法来发布OrderCreatedEvent事件,并将订单信息封装到事件对象中。
接下来,我们需要定义一个监听器来监听OrderCreatedEvent事件,并在事件发生时发送确认邮件给用户。示例代码如下:
@Componentpublic class OrderCreatedEventListener implements ApplicationListener<OrderCreatedEvent> { @Override public void onApplicationEvent(OrderCreatedEvent event) { Order order = event.getOrder(); // 发送确认邮件给用户 ... }}复制代码
在Spring框架中,自定义事件是一个非常重要的特性,它可以帮助我们实现各种复杂业务场景,例如订单创建事件、支付成功事件、邮件发送成功事件等。
举一个订单创建事件的例子。假设我们正在开发一个电商平台,我们需要在订单创建成功时,发送一封确认邮件给用户。为了实现这个功能,我们可以定义一个OrderCreatedEvent事件,并在订单创建时发布这个事件。具体代码如下:
首先,定义一个OrderCreatedEvent类,继承自ApplicationEvent类,用于表示订单创建事件:
javaCopy codepublic class OrderCreatedEvent extends ApplicationEvent { private Order order; public OrderCreatedEvent(Object source, Order order) { super(source); this.order = order; } public Order getOrder() { return order; }}复制代码
在订单创建成功时,我们可以通过ApplicationContext对象来发布一个OrderCreatedEvent事件,示例代码如下:
javaCopy code@Componentpublic class OrderService { @Autowired private ApplicationContext applicationContext; public void createOrder(Order order) { // 创建订单逻辑 ... // 发布订单创建事件 applicationContext.publishEvent(new OrderCreatedEvent(this, order)); }}复制代码
上述代码中,我们首先通过@Autowired注解注入了ApplicationContext对象,然后在订单创建成功后,通过applicationContext.publishEvent()方法来发布OrderCreatedEvent事件,并将订单信息封装到事件对象中。
接下来,我们需要定义一个监听器来监听OrderCreatedEvent事件,并在事件发生时发送确认邮件给用户。示例代码如下:
javaCopy code@Componentpublic class OrderCreatedEventListener implements ApplicationListener<OrderCreatedEvent> { @Override public void onApplicationEvent(OrderCreatedEvent event) { Order order = event.getOrder(); // 发送确认邮件给用户 ... }}复制代码
在上述代码中,我们定义了一个OrderCreatedEventListener监听器,实现了ApplicationListener接口,并重写了其中的onApplicationEvent()方法,用于接收OrderCreatedEvent事件的通知,并执行相应的逻辑,例如发送确认邮件给用户等。
通过这种方式,我们可以非常方便地实现订单创建事件的处理逻辑,并将处理逻辑与业务逻辑进行分离,从而使得代码更加模块化和易于维护。同时,我们还可以通过定义不同的自定义事件和监听器,来实现各种复杂的业务场景,例如支付成功事件、邮件发送成功事件等。
什么情况下使用观察者模式
- 当一个对象的改变需要同时改变其他多个对象的时候,使用观察者模式可以避免对象之间的紧密耦合。例如,在一个天气应用中,如果要在多个地方显示当前天气状况,我们可以使用观察者模式,将天气应用作为主题(Subject),将手机App、桌面App和网站作为观察者(Observer),以便在天气数据发生变化时,自动更新所有的观察者。
- 当一个对象的改变会触发连锁反应,引起其他多个对象的状态变化时,使用观察者模式可以帮助我们更好地管理这种连锁反应。例如,在一个电商应用中,如果用户下单成功后,需要触发一系列的事件,例如减少库存、生成订单、发送短信等,我们可以使用观察者模式,将订单作为主题(Subject),将库存管理器、订单管理器、短信发送器等作为观察者(Observer),以便在订单创建成功后,自动触发这些事件。
- 当一个对象的改变需要动态地添加或删除其他多个对象时,使用观察者模式可以帮助我们更好地管理这些动态变化。例如,在一个社交应用中,如果用户在发布一条动态时,可以选择将这条动态发布到某个圈子或者某些好友,我们可以使用观察者模式,将用户作为主题(Subject),将圈子和好友作为观察者(Observer),以便在用户选择发布圈子或好友时,动态地添加或删除对应的观察者。
总结:
观察者模式的优点:
- 解耦性好:观察者模式可以将主题(Subject)和观察者(Observer)解耦,从而使得它们之间的依赖关系变得松散。这样一来,主题和观察者就可以相互独立地变化,而不会对彼此造成影响。
- 可扩展性好:观察者模式可以很容易地添加新的观察者,从而实现更加灵活的扩展。这样一来,当新的观察者加入时,主题和已有的观察者不需要做任何修改,只需要添加新的观察者即可。
- 便于维护:观察者模式可以将逻辑分离,从而使得代码更加清晰、简洁和易于维护。这样一来,当需求变化时,我们只需要修改观察者的实现,而不需要修改主题的实现,从而避免了代码的耦合性。
- 易于实现:观察者模式的实现非常简单,只需要定义好主题和观察者的接口,并在主题中维护一个观察者列表,即可实现观察者模式的功能。
观察者模式的缺点:
- 过多的观察者会导致性能问题:当主题有过多的观察者时,会导致通知观察者的时间延长,从而影响性能。因此,在使用观察者模式时,需要注意观察者的数量,避免过多的观察者。
- 观察者和主题之间的关系可能难以理解:当观察者和主题之间的关系过于复杂时,可能会导致代码难以理解和维护。因此,在使用观察者模式时,需要注意代码的清晰度和简洁度。
总之,观察者模式是一种非常常用的设计模式,它可以帮助我们实现对象之间的松耦合,并使得代码更加灵活、可扩展和易于维护。虽然观察者模式存在一些缺点,但只要合理使用和设计,就可以最大化地发挥其优点,从而满足各种业务需求。