我的理解
很久之前有个老师给我们讲观察者模式时,举了一个例子:
妈妈照顾婴儿,他们在不同的房间,婴儿在婴儿房间A,妈妈在书房M,
为了保证婴儿睡醒之后能及时得到照顾,妈妈每隔10分钟就去房间A瞧瞧,看看婴儿是否醒了.
妈妈为了防止忘记,给自己定了一个闹钟,每隔10分钟响一次.
while (10分钟之后) {
watchBaby();//检查婴儿是否睡醒
}
一段时间之后,妈妈觉得很累,而且效率很低,因为很多次妈妈去照看婴儿时,发现婴儿在睡觉啥事也没有.
后来,妈妈发现了一个好方法,在婴儿身上寄一个铃铛,婴儿只要一动,铃铛就会响,妈妈就能听到.
这样妈妈就可以安心工作了,只要听到铃铛响就跑过去照看下.
关系图:
其实上面的例子涉及到两种模式:
- 妈妈每隔10分钟主动去查看 是轮询模式;
- 妈妈不用主动去检查,婴儿醒了,妈妈就会听到铃铛, 是观察者模式
那种模式效率更高呢?
一般情况下,观察者模式效率更高,
轮询,每隔一段时间去查看,浪费了资源.
观察者模式的角色
有如下角色:观察者,被观察者,事件,
有如下操作:注册,通知
观察者
案例中的妈妈就是观察者,她观察婴儿,看它醒了没有,有什么需求
被观察者
婴儿是被观察者,是被关注的对象.
一旦它发生什么事件,观察者就会有所响应
事件
观察者关注的事件就是铃铛是否响,铃铛一响说明婴儿醒了.婴儿一醒,妈妈就要去照顾,比如喂奶等.
注册(订阅)
妈妈在婴儿身上挂一个铃铛,就是注册事件.
一旦该事件发生,观察者就会有所响应.一旦婴儿醒了,妈妈就会听到铃铛声(收到通知)
通知
发生事件(婴儿醒了)时,被观察者(婴儿)会主动通知观察者(妈妈)
java 类图
示例
接口
观察者:
/**
* 接口描述: 观察者. <br />
*
* @author hanjun.hw
* @since 2018/11/6
*/
public interface Observer {
/**
* 观察者收到通知后,观察者进行响应
*
* @param observable
*/
void update(Observable observable);
}
被观察者:
public abstract class Observable {
/**
* 订阅观察者
*
* @param observer
*/
public abstract void register(Observer observer);
/**
* 通知观察者
*/
protected abstract void notifyAllObservers();
/**
* 观察者真正感兴趣的事件
*/
public void wakeUp() {
bellRinging();
notifyAllObservers();
}
/**
* 观察者观察的事件
*/
protected abstract void bellRinging();
}
观察者实现
/**
* 类描述: 具体观察者:妈妈. <br />
*
* @author hanjun.hw
* @since 2018/11/6
*/
public class Mother implements Observer {
@Override
public void update(Observable observable) {
System.out.println("婴儿醒了,去照看 :" + observable);
}
}
被观察者实现
/**
* 类描述: 被观察者:婴儿. <br />
*
* @author hanjun.hw
* @since 2018/11/6
*/
public class Baby extends Observable {
private HashSet<Observer> observers = new HashSet<>();
/**
* 把铃铛系在婴儿身上
* @param observer
*/
@Override
public void register(Observer observer) {
observers.add(observer);
}
/**
* 妈妈听到铃铛声,做出响应
*/
@Override
protected void notifyAllObservers() {
observers.forEach(o -> o.update(this));
}
@Override
protected void bellRinging() {
System.out.println("婴儿睡醒了 ,铃铛响了");
}
}
测试
public static void main(String[] args) {
Baby concreteObservable = new Baby();
Mother concreteObserver = new Mother();
concreteObservable.register(concreteObserver);
concreteObservable.wakeUp();
}
应用场景
Google eventbus
组成部分
事件(什么类型的事件)---对应铃铛响了
事件监听器,即事件处理程序(响应)----对应妈妈
注册事件监听器(register);----对应往婴儿身上挂铃铛
触发事件(trigger/post);---婴儿醒了,摇动了铃铛
实例
事件,可以是任何自定义对象
/**
* Created by whuanghkl on 17/6/22.<br />
* 自定义事件
*/
public class AccessLoggerEvent {
}
事件监听器
/**
* Created by whuanghkl on 17/6/22.<br />
* 事件监听器
*/
@Component
public class AccessLoggerListener {
@Resource
private EventBus eventBus;
/**
* 订阅
*/
@PostConstruct
public void init() {
eventBus.register(this);
}
@Subscribe
public void logEvent(AccessLoggerEvent event) {
System.out.println("logEvent");
}
}
事件监听器自己注册到eventBus
在控制器中触发事件
AccessLoggerEvent accessLoggerEvent = new AccessLoggerEvent();
eventBus.post(accessLoggerEvent);
婴儿是什么? 是上述代码(eventBus.post)所在类
java swing 按钮单击/文本框回车
JButton cancelButton = new JButton("Cancel");
cancelButton.setActionCommand("Cancel");
cancelButton.addActionListener(new ActionListener() {//ActionListener 是观察者
@Override
public void actionPerformed(ActionEvent e) {//观察者的响应
LoginDialog.this.dispose();
}
});
涉及的元素:
按钮 -----对应婴儿
按钮增加事件 ,即调用 addActionListener() ---对应往婴儿身上挂铃铛
用户点击按钮;---婴儿醒了,摇动了铃铛
执行 addActionListener()
执行 addActionListener() 添加的ActionListener ---对应 妈妈听到铃声之后去照看
妈妈是什么? 是ActionListener 对象
react 数据的双向绑定
参考:https://juejin.im/post/59f2e9b16fb9a04529360146
mq 消息队列订阅
多路复用机制epoll 也使用了观察者模式,
与之相对的select:整个socket集合会被遍历一次,比如集合里面有1024个socket,即便只有一个socket有数据可读,也遍历所有的1024个socket,这个是观察者模式的反例.
epoll的实现逻辑就是观察者模式的思想:
协议数据包到达网卡并被排入socket的接收队列。
睡眠在socket的睡眠队列wait_entry被唤醒,wait_entry_sk的回调函数epoll_callback_sk被执行。
epoll_callback_sk将当前socket插入epoll的ready_list中。
这样就不用遍历整个socket队列了,而只需要遍历ready_list
观察者模式的作用
解耦,比如 Google eventbus
提高响应速度(相对于轮询)
一般是辅助的容易变化的业务
观察者模式和mq(发布订阅) 的区别
mq:削峰,被观察者和观察者不用感知对方的存在;
观察者模式中,观察者和被观察者可以感知到对方的存在
思考
设计模式是经验的总结,是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结;
观察者模式其实在日常生活中也有体现,
比如银行业务 有短信通知,如果我绑定了手机号(订阅),那么银行卡有交易时(关注的事件发生),就会自动给我们发短信(响应)
既能把观察者模式理解成为一种思想,也能理解为解耦的一种策略