观察者模式(Observer Pattern)
1.场景引入
很久很久之前,我们获知世界大事都是要通过报纸的。而报社的业务就是出版报纸,向某家报社订阅报纸,只要他们有新报纸出版,就会给你送过来。当你不想再看报纸时,取消订阅,他们就不会再送新报纸过来了。
再比如,你在B站上关注了一个UP主,只要他一更新视频,你就会收到他的更新通知。不想再看时,则可以取关。
上面两种场景中体现的便是典型的观察者模式,这些场景主要分为两种角色,一种是发行报纸/更新视频的角色, 另一种则是订阅报纸/订阅更新的角色。前一种角色我们称之为主题(Subject),而后一种角色我们称之为观察者(Observer)。
2.定义
观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
解释一下:在现实世界中,许多对象并不是独立存在。一个主题对象状态发生变化时,许多依赖于该对象的其他观察者对象都需要发生变化。我们在类中可以模拟实现这种一对多依赖关系。具体做法是,当主题对象(Subject)内部的变量发生变化,它就去主动通知依赖于自己的所有观察者对象(Observer)。观察者模式可以将观察者和被观察的对象分离开,实现了两者之间的松耦合。
3.组成部分
1. 抽象主题(Subject):
把所有观察者对象的引用保存到一个集合里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
2. 具体主题(ConcreteSubject):
将有关状态变量存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出更新通知。
3. 抽象观察者(Observer):
为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
4. 具体观察者(ConcreteObserver):
实现抽象观察者所定义的更新接口,以便主题对象在状态更新时通知观察者对象。
4.具体实现
实现观察者模式有很多形式,比较常用的是使用“注册—通知—撤销注册”的方法。
具体做法是:主题对象维护一个内部状态字段和一个观察者列表,当主题对象的状态发生变化时,依次通知列表中的观察者。同时,观察者维护一个主题对象的引用,主要是实现从主题对象中进行主题以及撤销注册的操作。
好的!经过上面的许多介绍,相信大家都已经对观察者模式有所了解了,那么现在我们就开始实战一下!
具体场景是做一个B站UP主更新视频时,将更新信息推送到所有关注该UP主的用户上。
备注:我个人喜欢在接口名前加大写I(表示Interface),个人习惯,不喜勿喷!
第一步:定义抽象主题接口(ISubject接口)
/***
* 抽象主题接口
*/
public interface ISubject {
// 注册观察者
void registerObserver(IObserver observer);
// 移除观察者
void removeObserver(IObserver observer);
// 通知观察者
void notifyObservers();
// 更新内部状态
void updateVideo(String videoName);
}
第二步:定义具体的主题类(UpLoader类)
import java.util.ArrayList;
import java.util.List;
public class UpLoader implements ISubject {
private List<IObserver> observers; // 维护一个观察者列表,用来实现通知操作
private String videoName; // 维护一个内部状态,当更新视频时,videoName会发生变化
/**
* 构造器,用来初始化observers和videoName
*/
public UpLoader() {
this.observers = new ArrayList<>();
this.videoName = null;
}
/**
* uploader更新视频
* @param videoName
*/
public void updateVideo(String videoName) {
this.videoName = videoName;
notifyObservers(); // 通知所有的用户(观察者)
}
/**
* 用户(观察者)注册到该UpLoader上
* @param observer
*/
@Override
public void registerObserver(IObserver observer) {
observers.add(observer); // 添加到观察者列表上
}
/**
* 移除观察者
* @param observer
*/
@Override
public void removeObserver(IObserver observer) {
observers.remove(observer);
}
/**
* 通知所有的观察者
*/
@Override
public void notifyObservers() {
for (IObserver observer : observers) {
observer.update(videoName); // 通知用户(观察者)视频更新了
}
}
}
第三步:定义抽象观察者接口(IObserver接口)
public interface IObserver {
// 用来通知观察者主题状态发生更新
void update(String videoName);
// 取消关注主题对象
void removeUpLoader();
}
第四步:定义具体的用户观察者类(UserObserver类)
public class UserObserver implements IObserver {
private ISubject uploader; // 绑定的UP主
private String username; // 用户名
/**
* 构造器
* @param uploader
* @param username
*/
public UserObserver(ISubject uploader, String username) {
this.uploader = uploader;
this.username = username;
uploader.registerObserver(this); // 关注Up主
}
/**
* 通知该用户视频更新了
* @param videoName
*/
@Override
public void update(String videoName) {
System.out.println("奥力给!" + username + ",您关注的UP主更新视频了,视频名为:" + videoName + ",快去一键三连吧~~~");
}
/**
* 把Up主取消关注
*/
public void removeUpLoader() {
this.uploader.removeObserver(this);
}
}
第五步:功能测试(ObserverTest类)
public class ObserverTest {
public static void main(String[] args) {
// 构造一个UP主(主题)
ISubject uploader = new UpLoader();
// 建立三个用户(观察者)
IObserver user1 = new UserObserver(uploader, "小明");
IObserver user2 = new UserObserver(uploader, "小浩");
IObserver user3 = new UserObserver(uploader, "小志");
// up主第一次更新视频
uploader.updateVideo("小猪佩奇");
System.out.println("--------------------------------------------------------------------");
// user2小浩取消关注up主
user2.removeUpLoader();
// up主第二次更新视频
uploader.updateVideo("喜洋洋与灰太狼");
}
}
测试结果:
5.分析
-
观察者模式使用到的设计原则:(来源于《Head First 设计模式》)
-
为了交互对象之间的松耦合而努力。
主题对象无需知道观察者的具体细节,只知道观察者实现了Observer接口。
松耦合降低了主题和观察者对象之间的相互依赖,一方代码的改变不会影响另一方。
-
找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。
在观察者模式中,会变化的地方是主题的状态,以及观察者的数目和类型。当观察者的数目和类型发生变化时,并不会影响主题对象。
-
针对接口编程,而不是针对实现编程。
观察者可以利用主题的接口向主题注册以及注销,而主题利用观察者接口通知观察者。
-
多用组合,少用继承。
观察者模式将观察者列表组合进主题中,将主题组合进观察者中。
-
-
使用场景:
- 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面。可以将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。(一个方面为Subject,另一个方面为Observer)
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。(一个对象为Subject,其他对象为Observer)
- 一个对象必须通知其他对象,而并不知道这些对象是谁,类似于收听广播。(通知他人的对象为Subject,被通知对象为Observer)
- 链式触发机制:需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
-
优缺点:
-
优点:
- 观察者和被观察者是松耦合的。 (注意和完全耦合的区别)
- 观察者模式建立了一套触发机制。
-
缺点:
- 如果一个主题对象有很多的直接和间接的观察者的话,通知所有的观察者将会花费很长时间。
- 如果主题对象和观察者对象之间有循环依赖,会触发死循环调用,导致系统崩溃。
- 观察者只能知道主题已经发生的变化以及变化后的状态,而没有相应的机制让观察者对象知道主题是怎么发生变化的
-
优点:
-
补充:
-
Java API也有内置的观察者模式
java.util包中包含最基本的Observer接口与Observable类,它就相当于我们上面定义的Observer接口与Subject接口。但要注意的是Observable(被观察者)是一个类而不是接口,这限制了它的复用能力。
-
观察者模式和发布-订阅模式的区别
正如我上面在具体实现中提到的那样,发布订阅模式是最常用的一种观察者模式的实现。发布订阅模式里,发布者和订阅者,不是松耦合,而是完全解耦的。他们通过一个中间人Broker进行交互。如下图所示:
-
结尾词
好了,观察者模式的介绍就到此为止了,它的设计方式也比较简单,相信大家肯定已经完全了解了它的使用方法。
最后,希望本文能给大家带来帮助,谢谢!
参考文章:https://www.runoob.com/design-pattern/observer-pattern.html