我们想这样一个业务场景,在一站到底的舞台上,有两名学霸在进行终极battle,每一轮都是主持人提出一个问题,这个时候舞台(勉强这么理解吧)就是被观察者,两名选手就是观察者,观察面前的显示屏状态得到题目,然后进行作答,代码实现如下:
/**
* 问题实体类
* Created by guo on 2021/12/6 11:08
*/
@Data
public class Question {
// 提问人名字
private String username;
// 问题内容
private String content;
}
/**
* 被观察者
* Created by guo on 2021/12/6 11:07
*/
public class Stage extends Observable {
private String name = "一站到底";
private static Stage stage = null;
private Stage(){};
public static Stage getInstance(){
if(null == stage) {
stage = new Stage();
}
return stage;
}
public String getName(){
return name;
}
public void publishQuestion(Question qUestion) {
System.out.println(qUestion.getUsername() + "在" + this.name + "上提交了一个问题。");
setChanged();
notifyObservers(qUestion);
}
}
/**
* 观察者
* Created by guo on 2021/12/6 11:08
*/
public class Contestant implements Observer {
private String name;
public Contestant(String name){
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
Stage stage = (Stage) o;
Question question = (Question) arg;
System.out.println("=========================");
System.out.println(name + "你好! \n" + "您收到了一个来自“" + stage.getName() + "“的提问,问题如下:\n" + question.getContent() + "\n" + "提问者:"+question.getUsername());
}
}
Test测试类:
/**
* Created by maqingguo on 2021/12/6 11:10
*/
public class Test {
public static void main(String[] args) {
Stage stage = Stage.getInstance();
Contestant zhangsan = new Contestant("张三");
Contestant lisi = new Contestant("李四");
stage.addObserver(zhangsan);
stage.addObserver(lisi);
// 业务逻辑代码
Question question = new Question();
question.setUsername("主持人");
question.setContent("观察者模式的原理?");
stage.publishQuestion(question);
}
}
大家把这些代码粘下来,跑起来,会得到如下运行结果:
代码运行流程如下:
在这里面的重点是1:如何将观察者添加到被观察者的观察者队列;2:调用被观察者的publish方法如何通知观察者执行update方法,并且将参数传递过去。
为了搞清楚上面两点,我们需要先看看被观察者Stage继承的Observable类,源码及我的简易注释翻译如下:
public class Observable {
// 是否需要通知的标识
private boolean changed = false;
// 观察者们
private Vector<Observer> obs;
/** Construct an Observable with zero Observers. */
public Observable() {
obs = new Vector<>();
}
/**
* 添加观察者
*/
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
/**
* 删除某个观察者
*/
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
/**
* 无参的通知方法
*/
public void notifyObservers() {
notifyObservers(null);
}
/**
* 有参的通知方法
*/
public void notifyObservers(Object arg) {
/*
* a temporary array buffer, used as a snapshot of the state of
* current Observers.
*/
Object[] arrLocal;
synchronized (this) {
/*
* 如果不允许通知就返回
*/
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
/**
* 清空所有的观察者
*/
public synchronized void deleteObservers() {
obs.removeAllElements();
}
/**
* 修改状态,代表允许触发观察者的update
*/
protected synchronized void setChanged() {
changed = true;
}
/**
* 重置changed
*/
protected synchronized void clearChanged() {
changed = false;
}
/**
* 返回当前changed,看看是否已经改变
*/
public synchronized boolean hasChanged() {
return changed;
}
/**
* 返回观察者数量
*/
public synchronized int countObservers() {
return obs.size();
}
}
可以看到当我们调用 stage.addObserver()时,是把观察者添加到obs队列中,在执行setChanged()时是将状态置为true,代表可以发布事件,notifyObservers(question)时是先判断状态,然后执行每个观察者的update,从这里面我们也看出,观察者必须要实现Obersver接口类,而这个接口很简单,只是声明了一个接口update要去实现
public interface Observer {
/**
* This method is called whenever the observed object is changed. An
* application calls an <tt>Observable</tt> object's
* <code>notifyObservers</code> method to have all the object's
* observers notified of the change.
*
* @param o the observable object.
* @param arg an argument passed to the <code>notifyObservers</code>
* method.
*/
void update(Observable o, Object arg);
}
到目前为止,相信你也明白了观察者模式的原理,并且相信你也可以自己手写一个了。
当时我看完之后,我一直对这个Observable中的changed布尔值的存在,不能理解,不过我后来还是想到了一个场景:A线程改了被观察者的内容和状态,在去发布之前,恰好B线程又将被观察者复原了,那么正常情况下A线程的发布应该不需要被执行,但是如果没有这个changed布尔值,程序还是依然会去发布时间,而这显然不是我们想要的了,这也是为什么发布方法里面上了synchronized锁,并且在锁里面又加了changed的判断。
我想了想我们公司的项目目前的使用情况,发现这个模式并没有鸟用,因为他还是链式的去执行,并不能减少执行时间,都玩这个东西了,是kafka消息队列不香吗?
有疑问可以在评论区交流,作者看见会回复。
如需转载,请一定声明原处。