1、概述
观察者模式(Observer)是一种行为设计模式,它定义了对象之间的
一对多依赖关系
,当一个对象的状态发生改变时,其他所有依赖于它的对象都会自动被通知并更新。这个模式也被称为
发布/订阅模式
。
观察者模式通常用于
需要实现事件驱动系统的场景
中。例如,当用户订阅了一个新闻网站的推送服务后,当这个网站发布了新的文章时,它会将这些更新通知给所有的订阅用户。在这个例子中,订阅用户是观察者,新闻网站是被观察者。
观察者模式的好处是减少了对象之间的耦合性,使得它们可以独立地进行修改和扩展。同时,当我们需要为一个对象添加或删除观察者时,也变得更加容易。因此,观察者模式是一种非常有用的设计模式。
2、结构
观察者模式的结构通常由以下几个部分组成:
-
Subject
(目标):被观察者,它包含了所有的观察者,并提供了添加、删除和通知观察者的方法; -
Observer
(观察者):观察者,它定义了接收目标发送的通知并更新自己状态的方法; -
ConcreteSubject
(具体目标):具体的被观察者,它维护着一组观察者,并在状态发生变化时通知它们; -
ConcreteObserver
(具体观察者):具体的观察者,实现了Observer定义的接口,以便在接收到通知时更新自己的状态。
3、实现方式
3.1、案例引入
当微信公众号发布一条新的文章时,所有关注该公众号的微信用户都会收到推送通知。在这个例子中,微信公众号就是被观察者,而所有关注该公众号的微信用户则是观察者。 当微信公众号发布新的文章时,它会将这个状态变化(即发布了新的文章)通知给所有观察者,也就是微信用户,从而让他们第一时间了解到最新的内容。
3.2、结构分析
根据案例中的场景分析,可以将场景中的各个部分对应到观察者模式中的相应角色:
-
Subject
接口对应抽象被观察者(Subject)角色。 -
WeChatOfficialAccount
类对应具体被观察者(ConcreteSubject)角色。 -
Observer
接口对应抽象观察者(Observer)角色。 -
WeChatUser
类对应具体观察者(ConcreteObserver)角色。
3.3、具体实现
以下是使用Java语言实现观察者模式的具体代码,实现微信公众号发布新文章通知所有关注该公众号的微信用户:
-
定义被观察者接口
Subject
和观察者接口
Observer
,其中
Subject
接口定义了三个方法:注册观察者、移除观察者和通知所有观察者;
Observer
接口定义了一个方法:更新状态。
// 定义被观察者接口
interface Subject {
// 注册观察者
void registerObserver(Observer observer);
// 移除观察者
void removeObserver(Observer observer);
// 通知所有观察者
void notifyObservers(String articleTitle);
}
// 定义观察者接口
interface Observer {
void update(String articleTitle); // 更新状态
}
-
微信公众号类
WeChatOfficialAccount
实现了被观察者接口
Subject
。它维护了一个观察者列表,实现了注册观察者、移除观察者和通知所有观察者的方法,并在发布新文章时通知所有观察者。
// 微信公众号类作为被观察者,实现Subject接口
class WeChatOfficialAccount implements Subject {
private List<Observer> observers = new ArrayList<>();
private String articleTitle;
// 注册观察者
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
// 移除观察者
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
// 通知所有观察者
@Override
public void notifyObservers(String articleTitle) {
for (Observer observer : observers) {
observer.update(articleTitle);
}
}
// 发布新文章,通知所有观察者
public void publishArticle(String articleTitle) {
this.articleTitle = articleTitle;
System.out.println("微信公众号发布了新文章:" + articleTitle);
notifyObservers(articleTitle);
}
}
-
微信用户类
WeChatUser
实现了观察者接口
Observer
。它在接收到微信公众号发布新文章的通知时,更新状态,即打印接收到的文章标题。
// 微信用户类作为观察者,实现Observer接口
class WeChatUser implements Observer {
private String userName;
public WeChatUser(String userName) {
this.userName = userName;
}
// 更新状态,即打印接收到的文章标题
@Override
public void update(String articleTitle) {
System.out.println(userName + "收到了新文章通知:" + articleTitle);
}
}
-
测试代码
TestObserverPattern
中,首先创建了一个微信公众号对象和三个微信用户对象,并让这三个用户都关注了该公众号。然后,微信公众号发布新文章,通知所有关注该公众号的微信用户。接着,其中一个微信用户取消了对该公众号的关注。最后,微信公众号再次发布新文章,通知所有关注该公众号的微信用户。
public class TestObserverPattern {
public static void main(String[] args) {
// 创建微信公众号对象
WeChatOfficialAccount officialAccount = new WeChatOfficialAccount();
// 创建微信用户对象
WeChatUser zhangsan = new WeChatUser("张三");
WeChatUser lisi = new WeChatUser("李四");
WeChatUser wangwu = new WeChatUser("王五");
// 微信用户关注微信公众号
officialAccount.registerObserver(zhangsan);
officialAccount.registerObserver(lisi);
officialAccount.registerObserver(wangwu);
// 微信公众号发布新文章
officialAccount.publishArticle("观察者模式在Java中的应用");
// 微信用户李四取消关注微信公众号
officialAccount.removeObserver(lisi);
// 微信公众号再次发布新文章
officialAccount.publishArticle("学习Java多线程编程");
}
}
整个程序运行后,输出结果为:
微信公众号发布了新文章:观察者模式在Java中的应用
张三收到了新文章通知:观察者模式在Java中的应用
李四收到了新文章通知:观察者模式在Java中的应用
王五收到了新文章通知:观察者模式在Java中的应用
微信公众号发布了新文章:学习Java多线程编程
张三收到了新文章通知:学习Java多线程编程
王五收到了新文章通知:学习Java多线程编程
4、观察者模式优缺点
观察者模式的优点:
-
降低了系统耦合度
:被观察者对象和观察者对象之间是松耦合关系,它们之间仅仅通过接口进行交互,可以降低系统中各个对象之间的耦合度,提高系统的可扩展性和可维护性; -
支持广播通信
:一旦被观察者对象发生变化,它会自动通知所有观察者对象,从而实现了广播通信; -
符合开闭原则
:当需要在系统中添加新的观察者时,只需要定义一个新的观察者类并实现抽象观察者接口即可,不需要修改原有代码,符合开闭原则; -
可以建立多层次触发链
:观察者对象除了可以观察被观察者对象外,还可以是其他观察者对象的被观察者对象,从而实现多层次触发链。
观察者模式的缺点:
-
观察者太多,通知开销大
:当观察者对象数量较多时,被观察者对象每次状态改变时都需要通知所有观察者对象,这将导致通知开销很大; -
循环依赖问题
:当观察者对象之间存在循环依赖时,系统的运行可能会出现问题; -
无法保证通知顺序
:由于被观察者和观察者之间是松耦合关系,观察者对象的调用顺序不能保证。
优点 | 缺点 |
---|---|
降低系统耦合度 | 观察者太多,通知开销大 |
支持广播通信 | 循环依赖问题 |
符合开闭原则 | 无法保证通知顺序 |
可以建立多层次触发链 |
与此同时,针对观察者模式的缺点,可以采取以下优化的手段:
-
减少通知开销
:如果被观察者状态变化太频繁,可以考虑采用“节流”或“防抖”的方式减少通知次数。例如,当被观察者状态连续变化时,只在最后一次变化后通知所有观察者; -
避免循环依赖
:可以通过定义一个中间对象来解决循环依赖问题,即让观察者对象与中间对象建立观察者-被观察者关系,中间对象与被观察者对象建立观察者-被观察者关系,从而避免存在直接的循环依赖; -
确保通知顺序
:可以在具体被观察者类中增加一个排序列表,每个观察者对象在注册时指定一个排序号,被观察者对象通知观察者时按照排序号的大小进行通知; -
采用异步通知
:将观察者的更新操作放到异步任务队列中执行,避免阻塞主线程,提高系统的响应速度和稳定性; -
使用事件总线框架
:可以使用事件总线框架来处理观察者模式中的通知问题,事件总线框架可以将观察者模式中的通知转化为事件,并在事件总线上进行管理和分发,从而实现了更高效的消息传递机制。
5、应用场景
观察者模式适用于以下场景:
-
当需要将一个对象的状态变化
通知给其他多个对象
时,可以使用观察者模式; -
当一个对象的改变需要
同时改变其他对象的状态或者操作
时,可以使用观察者模式; -
当
一个抽象模型
有两个方面,其中
一个方面依赖于另一个方面
时,可以使用观察者模式。例如,模型和视图之间的关系,模型驱动视图的更新,而视图反过来也要反映到模型中; -
当一个对象必须通知其他对象,但是你
不知道
这些对象是谁时,可以使用观察者模式。例如,你不知道哪些用户订阅了你的微信公众号,但你需要在每次发布新文章时通知所有订阅用户; -
当一个对象需要有别的对象来协助完成某项工作,并且
不希望知道
具体协助它的对象时,可以使用观察者模式。例如,一个员工完成某项任务需要得到经理的批准,但他并不知道具体是哪个经理会批准。
观察者模式在Java和常见的框架中十分常见
在Java和Spring中,观察者模式被广泛应用。以下是几个常见的例子:
-
Java内置的事件处理机制
:Java提供了一套事件处理机制,其中就包括观察者模式的实现。例如,当用户点击一个按钮时,该按钮会生成一个事件(如ActionEvent),并通知所有注册的事件监听器(如ActionListener)执行相应的回调方法; -
Swing GUI编程框架
:Swing是Java提供的GUI编程框架,它使用观察者模式来处理用户界面和程序之间的交互。例如,当用户输入数据时,文本框会生成一个事件(如TextEvent),并通知所有注册的事件监听器(如TextListener)执行相应的回调方法; -
Spring框架中的事件机制
:Spring框架也提供了一套事件机制,其中就包括观察者模式的实现。例如,当某个业务逻辑发生变化时,可以通过发布相关的事件(如ApplicationEvent),并通知所有注册的事件监听器(如ApplicationListener)执行相应的回调方法。