说到观察者模式,在学习这个模式的过程中,我联想了许多观察者模式的应用场景,这个模式应该算项目中最有可能会用到的一个模式吧,比如在早年的YY直播横行的年代,我一开电脑就是先打开YY,因为我的YY里订阅了很多喜欢的主播,在主播开播的时候,YY自动会推送我一个开播提醒,我想,这就应该是观察者模式了吧。(订阅-发布模式)
定义:观察者模式(有时又被称为发布-订阅模式、模型-视图模式、源-收听者模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实作事件处理系统。
以上定义与类图来自百度百科
由类图与定义我们可以知道,观察者模式就是有一个类管理所有观察者(持有一个观察者列表),并在被观察者发生变化时调用方法notify()通知那些观察者列表里的所有观察者,同时观察者需要实现一个观察者接口,才可以订阅以及被接收通知。
在JDK中,有现成的观察者接口与一个被观察的类。下面截取部分代码。
//观察者接口
public interface Observer {
//这个方法是观察者在观察对象产生变化时所做的响应动作,参数分别为观察的对象和一个预留参数
void update(Observable o, Object arg);
}
import java.util.Vector;
//被观察者类
public class Observable {
//用来标记和判断被观察者是否发生了变化
private boolean changed = false;
//一个观察者的列表
private Vector obs;
public Observable() {
obs = new Vector();
}
//notifyObservers(Object arg)的重载方法
public void notifyObservers() {
notifyObservers(null);
}
//通知观察者我已经变化了,你需要调用你的update方法了
public void notifyObservers(Object arg) {
//用来存放观察者当时的状态,有点类似与备忘录模式,记录当前观察者的状态,在后面通知时如果有变化也不受影响
Object[] arrLocal;
//这个同步块同步了获取观察者列表的过程,期间无法对观察者列表做改动了
synchronized (this) {
//判断被观察者是否发生变化了
if (!changed)
return;
//将此时的所有的观察者放入上面的数组
arrLocal = obs.toArray();
//重置状态
clearChanged();
}
//此时已经得到了所有需要通知的观察者的"备忘录数组",此时如果有观察者取消订阅之类的操作,都不影响通知之前所有观察者调用update
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
//删除所有观察者,类似取消订阅
public synchronized void deleteObservers() {
obs.removeAllElements();
}
//标记被观察者发生变化了
protected synchronized void setChanged() {
changed = true;
}
//重置发生变化的标记
protected synchronized void clearChanged() {
changed = false;
}
//判断观察者是否变化了
public synchronized boolean hasChanged() {
return changed;
}
}
上面的Observer就是类图中的Observer抽象观察者接口,Observable就是类图中的Subject,拥有一个观察者的列表,有一个notify的方法用于通知所有列表中的观察者。
接下来举个例子,就是开头所说的直播订阅。下面是具体的观察者类。
package test;
import java.util.Observable;
import java.util.Observer;
//观察者类,就是观众类,需要实现Observer接口
public class Viewer1 implements Observer {
String name;
public Viewer1(String name) {
this.name = name;
}
// 重写update做的方法
@Override
public void update(Observable o, Object arg) {
if(o instanceof Player) {
Player player = (Player) o;
System.out.println(player.getName() + "开播啦,房间标题为" + player.getTitle() + name + "快去看看吧!");
}
}
// 订阅一个主播
public void subscribe(String name) {
Platform.getInstance().subscribe(name, this);
}
}
这里我写了一个中间的平台类,类似某鱼TV,用来存放所有的主播,以及提供一个订阅的功能。
package test;
import java.util.HashMap;
import java.util.Map;
import java.util.Observer;
public class Platform {
//存放所有主播
public Map<String, Player> players = new HashMap<String, Player>();
//单例模式
private Platform() {
}
public static Platform getInstance() {
return Instance.platformInstance;
}
private static class Instance {
static Platform platformInstance = new Platform();
}
//在平台上添加主播
public void addPlayer(Player player) {
players.put(player.getName(), player);
}
//一个订阅主播的方法
public void subscribe(String name, Observer observer) {
players.get(name).addObserver(observer);
}
}
下面就是具体的主播类了。
package test;
import java.util.Observable;
//主播类,需要继承被观察者类
public class Player extends Observable {
String name;
String title;
//在构造函数里向平台添加主播
public Player(String name) {
this.name = name;
Platform.getInstance().addPlayer(this);
}
//主播上线之后会调用的方法,在这里通知所有订阅了该主播的观众
public void online(String title) {
System.out.println(name + "上线开播! 房间名:" + title);
this.title = title;
setChanged();
notifyObservers();
}
public String getName() {
return name;
}
public String getTitle() {
return title;
}
}
下面测试一下功能吧
package test;
public class testA {
public static void main(String[] args) {
// 有两个主播分别为uzi和董小飒~
Player uzi = new Player("uzi");
Player dongxiaosa = new Player("dongxiaosa");
// 有三个用户
Viewer1 viewer1 = new Viewer1("张三");
Viewer1 viewer2 = new Viewer1("李四");
Viewer1 viewer3 = new Viewer1("王五");
// 他们分别订阅了主播,其中第三个观众谁都没订阅
viewer1.subscribe("uzi");
viewer1.subscribe("dongxiaosa");
viewer2.subscribe("uzi");
// 主播开播啦
uzi.online("你的迟到很危险!");
dongxiaosa.online("今天玩玩LOL");
}
}
控制台打印
个人觉得这个模式还是蛮好玩的,总结一下吧,这里的主播类并不需要关心我具体要通知谁,只需要知道要通知的人实现了一个观察者接口,所以订阅的观众需要实现一个观察者的接口。这个模式分离了观察者和被观察者的责任,就像观众只需要订阅即可,不用关心其他事情,主播只需要notify即可通知,不需要知道具体要通知谁谁谁了,因为具体的通知列表,在观众那边就已经操作好了(订阅和取消订阅)。
注:由于我的水平有限,有些地方说的可能有问题?欢迎大家指出,互相讨论互相学习进步!