欢迎来到设计模式系列的第十三篇文章!在之前的文章中,我们学习了许多常用的设计模式,今天我们将介绍观察者模式,它是一种行为型设计模式,用于定义对象之间的一对多依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。
在学习观察者模式前,我们可以带着一下三个问题来学习:
1.谁是观察者,谁又是被观察者?
2.观察者如何”观察”被观察者的?
3.为什么要使用观察着模式?
4.你在工作中见过哪些观察者模式?
观察者模式简介
观察者模式是一种常用的设计模式,它用于构建对象之间的发布-订阅(Publish-Subscribe)关系。在观察者模式中,有两类核心角色:
- 主题(Subject):主题是被观察的对象,它维护了一个观察者列表,可以动态地添加或删除观察者。主题通常具有一种状态,当状态发生变化时,会通知所有观察者。
- 观察者(Observer):观察者是依赖于主题的对象,它们会注册到主题上,以便在主题的状态发生变化时得到通知并执行相应的操作。
观察者模式的核心思想是降低主题和观察者之间的耦合度,使得它们可以独立地变化。这种松耦合的设计可以更好地支持可维护性和可扩展性。
为什么需要观察者模式?
在软件开发中,经常会遇到一对多的场景,例如:
- 一个新闻网站需要通知多个订阅者(用户)有新文章发布。
- 一个股票市场应用需要通知多个投资者股票价格的变化。
- 一个气象站需要通知多个应用天气信息的变化。
如果没有观察者模式,我们可能会采用硬编码的方式来实现这些通知,但这样会导致高耦合和不易维护的问题。观察者模式通过将主题和观察者分离,使得它们可以独立变化,从而更好地应对这类场景。
观察者模式的实现
观察者模式的实现通常包括以下几个关键元素:
- 主题接口(Subject):定义了主题对象的基本操作,包括注册观察者、删除观察者和通知观察者等。
- 具体主题(ConcreteSubject):实现了主题接口,并维护了一个观察者列表。具体主题通常具有一个状态,当状态发生变化时,会通知所有注册的观察者。
- 观察者接口(Observer):定义了观察者对象的更新操作,通常包括一个
update
方法。 - 具体观察者(ConcreteObserver):实现了观察者接口,并注册到具体主题上。当主题状态发生变化时,具体观察者的
update
方法会被调用。
现在,让我们通过一个示例来演示观察者模式的实现。假设我们正在开发一个简单的股票市场应用,股票价格会不断变化,我们需要通知多个投资者股票价格的变化情况。
首先,我们定义观察者接口 Observer
:
public interface Observer {
void update(double price);
}
然后,我们定义主题接口 Subject
:
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
接下来,我们创建一个具体主题 StockMarket
,它继承了 Subject
接口:
import java.util.ArrayList;
import java.util.List;
public class StockMarket implements Subject {
private List<Observer> observers = new ArrayList<>();
private double price;
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(price);
}
}
public void setPrice(double price) {
this.price = price;
notifyObservers();
}
}
在 StockMarket
类中,我们维护了一个观察者列表 observers
和股票价格 price
。当 setPrice
方法被调用时,会通知所有注册的观察者。
接下来,我们创建一个具体观察者 Investor
,它实现了 Observer
接口:
public class Investor implements Observer {
private String name;
public Investor(String name) {
this.name = name;
}
@Override
public void update(double price) {
System.out.println(name + " 收到股票价格更新,当前价格为 " + price);
}
}
最后,我们可以测试观察者模式的效果:
public class Main {
public static void main(String[] args) {
StockMarket stockMarket = new StockMarket();
Investor investor1 = new Investor("Alice");
Investor investor2 = new Investor("Bob");
stockMarket.registerObserver(investor1);
stockMarket.registerObserver(investor2);
stockMarket.setPrice(100.0);
stockMarket.setPrice(105.0);
stockMarket.removeObserver(investor1);
stockMarket.setPrice(110.0);
}
}
以上代码创建了一个股票市场 StockMarket
和两个投资者 Investor
,并演示了股票价格的变化如何通知投资者。
观察者模式的优点
观察者模式具有许多优点,使其成为软件开发中常用的设计模式之一:
- 降低耦合度:观察者模式将主题和观察者分离,主题不需要知道观察者的具体细节,从而降低了它们之间的耦合度。
- 支持广播通信:主题状态变化时,可以通知多个观察者,实现了一对多的通信,方便信息广播。
- 开闭原则:通过增加新的观察者类和主题类,可以扩展观察者模式,符合开闭原则。
- 可维护性:因为观察者和主题之间的关系是松散的,所以更容易维护和修改。
观察者模式的缺点
观察者模式也存在一些缺点,需要考虑:
- 观察者太多时性能问题:如果观察者太多,通知所有观察者可能会影响性能,尤其是在大规模系统中。
- 顺序问题:观察者的通知顺序可能不确定,如果有顺序要求,需要额外处理。
- 可能导致循环依赖:主题和观察者之间的循环依赖可能引入问题,需要小心处理。
观察者模式的应用场景
观察者模式适用于以下场景:
- 一对多的依赖关系:当一个对象的状态变化需要通知多个其他对象时,观察者模式非常适用。例如,新闻发布、股票市场更新等。
- 抽象模型与实现分离:当需要将抽象模型与其具体实现分离时,观察者模式可以帮助实现这种分离。例如,图形界面框架中的事件处理。
- 动态系统:在动态系统中,对象的数量和类型可能会随时改变,观察者模式允许动态地添加或删除观察者。
观察者模式的实际应用
观察者模式在现实世界和软件开发中都有广泛应用。以下是一些实际应用示例:
- 邮件订阅:邮件订阅服务是观察者模式的一个典型应用。用户可以订阅不同类型的邮件通知,当有新邮件到达时,订阅者会收到通知。
- 社交媒体通知:社交媒体平台可以通知用户关注的人或页面的更新,例如,新的帖子、消息或评论。
- 股票市场应用:股票市场应用通常使用观察者模式来实时通知投资者股票价格的变化。
- 事件处理:图形用户界面(GUI)框架使用观察者模式来处理用户事件,例如,鼠标点击、键盘输入等。
最佳实践
在使用观察者模式时,有一些最佳实践值得注意:
- 避免循环依赖:确保主题和观察者之间没有循环依赖,以防止潜在的问题。
- 考虑多线程情况:如果在多线程环境中使用观察者模式,确保实现线程安全的方式来处理观察者列表和状态更新。
- 谨慎使用广播通知:通知所有观察者可能会影响性能,如果只有部分观察者关心状态变化,可以考虑使用条件通知。
想进一步了解观察者模式的老铁可以了解一下 spring中的事件机制:深入理解事件发布监听机制
总结
观察者模式是一种非常有用的设计模式,用于实现对象之间的松耦合通信。通过定义一对多的依赖关系,主题状态变化时通知多个观察者,实现了对象之间的订阅机制。在实际应用中,观察者模式可以帮助我们构建灵活、可扩展的系统。