1 定义
观察者模式(Observer Pattern)属于行为型设计模式,它主要用于被观察者对象与观察者对象的通讯,被观察者与观察者的关系是一对多的依赖关系,当被观察者状态发生改变时,所有依赖它的观察者都能自动收到改变的通知并做出各自相应的逻辑处理。
发布/订阅模式(Publish/Subscribe Pattern)是后来观察者模式的一个进化版,它也可算是观察者模式的一个别称。
发布/订阅模式跟观察者模式本质上都是为了解决一个对象跟多个对象的通迅,其最大的区别在于发布者(被观察者)跟订阅者(观察者)是完全解耦的,它们不需要知道彼此的存在,发布者(被观察者)和订阅者(观察者)在调度中心的帮助下完成消息的通讯,调度中心作为一个中间件接收发布者(被观察者)传入的消息后再分发给相应的订阅者(观察者)。
这种一对多的通迅方式类例于现实场景中,中央人民广播电台在播放一新闻节目,它不关心是谁在收听节目,而我们所有人只需要将收音机调至该电台对应的频道便能接听到电台的节目一样。
2 观察者模式实现
观察模式一般包含了4个角色,分别是:
- 抽象主题(Subject):用于把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任意数量的观察者,它并提供增加和删除观察者对象的方法。例如上述定义举例的广播电台。
- 具体主题(ConcreteSubject):用于当本身发生状态改变时,给所有登记过的具体观察者发出通知。例如上述定义举例中的中央人民广播电台。
- 抽象观察者(Observer):用于为所有的具体观察者定义通用接口方法。例如上述定义举例的接听广播的设备。
- 具体观察者(ConcreteObserver):实现抽象观察者中要求更新的接口,以便使本身的状态与主题的状态相协调。例如上述定义举例的具体哪个人或哪个组织的设备在接听广播。
2.1 通用实现
抽象主题类,内部维护着一个观察者的数组,可对数组进行添加、删除以及遍历执行发送广播:
public abstract class RadioStation {
protected String mMessage;
protected List<IEquipment> mIEquipmentList = new ArrayList<>();
public RadioStation(String message) {
mMessage = message;
}
protected void add(IEquipment IEquipment) {
mIEquipmentList.add(IEquipment);
}
protected void remove(IEquipment IEquipment) {
mIEquipmentList.remove(IEquipment);
}
protected void broadcast() {
for(IEquipment IEquipment : mIEquipmentList) {
IEquipment.onReceive(mMessage);
}
}
}
具体主题类,它是具体的广播电台:
public class ChinaNationalRadio extends RadioStation {
public ChinaNationalRadio() {
super("中央人民广播电台-中国之声");
}
@Override
public void broadcast() {
super.broadcast();
}
}
抽象观察类,声明了接收广播的方法:
public interface IEquipment {
void onReceive(String message);
}
具体观察类,它可以是个人的收音机设备,也可以是学校的广播室收音设备:
public class PersonalEquipment implements IEquipment {
@Override
public void onReceive(String message) {
System.out.println("个人在接收广播:" + message);
}
}
public class SchoolEquipment implements IEquipment {
@Override
public void onReceive(String message) {
System.out.println("学校在接收广播:" + message);
}
}
客户端:
public class Main {
public static void main(String[] args) {
RadioStation chinaNationalRadio = new ChinaNationalRadio();
IEquipment personalEquipment = new PersonalEquipment();
IEquipment schoolEquipment = new SchoolEquipment();
chinaNationalRadio.add(personalEquipment);
chinaNationalRadio.add(schoolEquipment);
chinaNationalRadio.broadcast();
}
}
输出结果:
个人在接收广播:中央人民广播电台-中国之声
学校在接收广播:中央人民广播电台-中国之声
2.2 Java中的实现
在 Java 中,可以通过 java.util.Observable 类和 java.util.Observer 接口来简单快速实现观察者模式,尽赶在JDK9之后已经被标记为@Deprecated,不推荐使用它们,原因是:Observer和Observable支持的事件模型非常有限,Observable传递的通知顺序未指定,并且状态更改与通知不一一对应。但是还是有必要去了解一下它们的使用:
具体主题类,它是具体的广播电台:
public class ChinaNationalRadio extends Observable {
public void broadcast() {
super.setChanged();
super.notifyObservers("中央人民广播电台-中国之声");
}
}
具体观察类,它可以是个人的收音机设备,也可以是学校的广播室收音设备:
public class PersonalEquipment implements Observer {
@Override
public void update(Observable observable, Object o) {
System.out.println("个人在接收广播:" + o);
}
}
public class SchoolEquipment implements Observer {
@Override
public void update(Observable observable, Object o) {
System.out.println("学校在接收广播:" + o);
}
}
客户端:
public class Main {
public static void main(String[] args) {
ChinaNationalRadio chinaNationalRadio = new ChinaNationalRadio();
Observer personalEquipment = new PersonalEquipment();
Observer schoolEquipment = new SchoolEquipment();
chinaNationalRadio.addObserver(personalEquipment);
chinaNationalRadio.addObserver(schoolEquipment);
chinaNationalRadio.broadcast();
}
}
输出结果:
个人在接收广播:中央人民广播电台-中国之声
学校在接收广播:中央人民广播电台-中国之声
3 发布/订阅模式实现
发布/订阅模式一般包含5个角色,分别是:
- 抽象发布(Publish):用于定义发布的动作。例如上述定义举例的广播电台。
- 具体发布(ConcretePublish):用于当本身发生状态改变时,指定调度中心进行发布动作。例如上述定义举例中的中央人民广播电台要进行电台广播。
- 抽象订阅(Subscribe):用于为所有的具体订阅定义通用的订阅、取消订阅和接收发布的接口方法。例如上述定义举例的接听广播的设备。
- 具体订阅(ConcreteSubscribe):实现抽象订阅定中要求接收的接口。例如上述定义举例的具体哪个人或哪个组织的设备在接听广播。
- 调度中心(DispatchCenter):用于发布和订阅之间的消息传递。例如上述定义举例的频道。
抽象发布类,定义一个发布动作,也就是发广播:
public abstract class RadioStation {
protected String mMessage;
public RadioStation(String message) {
mMessage = message;
}
abstract void broadcast(RadioChannel radioChannel);
}
具体发布类,它是具体的广播电台拥有发广播动作:
public class ChinaNationalRadio extends RadioStation {
public ChinaNationalRadio() {
super("中央人民广播电台-中国之声");
}
@Override
public void broadcast(RadioChannel radioChannel) {
radioChannel.broadcast(mMessage);
}
}
抽象订阅类,声明了订阅频道、取消订阅频道和接收广播的方法:
public abstract class Equipment {
protected void subscribe(RadioChannel radioChannel) {
radioChannel.subscribe(this);
}
protected void unsubscribe(RadioChannel radioChannel) {
radioChannel.unsubscribe(this);
}
abstract void onReceive(String message);
}
具体订阅类,它可以是个人的收音机设备,也可以是学校的广播室收音设备:
public class PersonalEquipment extends Equipment {
@Override
public void onReceive(String message) {
System.out.println("个人在接收广播:" + message);
}
}
public class SchoolEquipment extends Equipment {
@Override
public void onReceive(String message) {
System.out.println("学校在接收广播:" + message);
}
}
调度中心类,它是就是电台广播中的频道:
public class RadioChannel {
private List<Equipment> mEquipmentList = new ArrayList<>();
public void subscribe(Equipment equipment) {
mEquipmentList.add(equipment);
}
public void unsubscribe(Equipment equipment) {
mEquipmentList.remove(equipment);
}
protected void broadcast(String message) {
for(Equipment Equipment : mEquipmentList) {
Equipment.onReceive(message);
}
}
}
客户端:
public class Main {
public static void main(String[] args) {
RadioChannel radioChannel = new RadioChannel();
Equipment personalIEquipment = new PersonalEquipment();
Equipment schoolIEquipment = new SchoolEquipment();
personalIEquipment.subscribe(radioChannel);
schoolIEquipment.subscribe(radioChannel);
RadioStation chinaNationalRadio = new ChinaNationalRadio();
chinaNationalRadio.broadcast(radioChannel);
}
}
输出结果:
个人在接收广播:中央人民广播电台-中国之声
学校在接收广播:中央人民广播电台-中国之声
4 总结
无论是观察者模式还是发布/订阅模式,它们都是为了当一个对象状态发生改变时,会多个曾经建立过一套触发机制的对象进行相应的通知,而且使得观察者和被观察者是抽象耦合的,通常在消息队列、事件委托、广播通迅方面的处理机制上比较常见。使用上还需要注意观察者如果过多会引起遍历时间的花费和要避免观察者和被观察者循环引用的情况而导致死循环。