背景
观察者模式你肯定知道并且用过,如果你没听过观察者模式这几个词,那发布-订阅模型你肯定知道。我们在使用Kafka等消息中间件时,就用到了发布-订阅模式进行数据的生产消费。你可以将发布-订阅模式理解为观察者模式。
如下代码:
kafka发布消息:
ListenableFuture future = kafkaTemplate.send(topic, jsonString);
消费者订阅消息:
@KafkaListener(topics = "${spring.kafka.topic}")
public void listen(ConsumerRecord<?, ?> record) {
log.info("topic={}, offset={}, message={}", record.topic(), record.offset(), record.value());
}
上面就是kafka发布-订阅的使用方法。
除了消息中间件的发布-订阅,zookeeper的watch机制也使用到了观察者模式。各位可以思考下,你在什么情况下会使用观察者模式呢?
什么是观察者模式
在现实生活中,许多对象都不是独立存在的,其中一个对象的改变往往会导致其它对象的改变。比如:到了下班时间你会下班回家,路上遇到红灯你会停下来,股市行情好了你会追加投资。
如果你在开发中要描述上述的关联关系,就可以使用观察者模式。
“Define a one-to-many dependency between objects so that when one object changes state,all its dependents are notified and updated automatically.(定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。)
”
观察者模式的主要由4个要素组成:
“”
- 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
- 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
- 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
结构图如下:
观察者模式
代码实现
Subject
Observer
ConcreteSubject
ConcreteObserver
测试代码如下:
@Test
public void test() {
Subject subject=new ConcreteSubject();
Observer obs1=new ConcreteObserver1();
Observer obs2=new ConcreteObserver2();
subject.add(obs1);
subject.add(obs2);
subject.notifyObserver();
}
测试结果如下:
具体目标发生改变...
--------------
具体观察者1作出反应!
具体观察者2作出反应!
关于观察者模式的思考
我们什么情况下可以使用观察者模式呢?
如果对象之间存在一对多关系,一个对象的状态发生改变会影响其他对象,我们就能使用观察者模式。我举个例子,如果某个商品出现质量问题。我们需要对已经购买该商品的订单冻结,那么我们就可以使用观察者模式。我们对该商品执行冻结命令,冻结该商品的同时,所有包含该商品的订单都会被通知并冻结。
我们再思考一下,在另一个场景中。用户下单后,需要送积分,生成物流信息,短信通知,微信通知,是不是可以使用观察者模式呢?关于这个问题我之前还写了一篇文章探讨使用观察者模式的好处,有兴趣的可以看看。
“”
不知你发现了没有,观察者和被观察者之间的耦合度很低。这样观察者和被观察者很容易扩展。
观察者模式很容易实现一条触发链。什么是触发链呢?比如早上闹钟把你闹醒,你就起床,你肚子饿了,你就会去吃早餐,快到上班时间了,你就会开车去上班,上班时间到了,你就打开电脑开始摸鱼。这一连串的的触发机制就形成了一个触发链。
观察者模式需要考虑一下开发效率和运行效率问题,一个被观察者,多个观察者,开发和调试就会比较复杂,而且在Java中消息的通知默认是顺序执行,一个观察者卡壳,会影响整体的执行效率。在这种情况下,一般考虑采用异步的方式。