本文最后更新于:2021年6月15日 晚上
本文介绍观察者模式在项目中的实际应用。
类型:行为型模式
意图:一对多关系依赖的多个对象,当一个对象状态发生改变,所有依赖的对象都可以得到通知并自动更新
主要解决:降低对象间的关联依赖性
观察者模式也称为发布订阅模式,监听器模式。
设计模式系列文章目录
角色
抽象主题(Subject)角色:抽象主题角色提供维护一个观察者对象聚集的操作方法,对聚集的增加、删除等。
具体主题(ConcreteSubject)角色:将有关状态存入具体的观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色负责实现抽象主题中聚集的管理方法。
抽象观察者(Observer)角色:为具体观察者提供一个更新接口。
具体观察者(ConcreteObserver)角色:存储与主题相关的自洽状态,实现抽象观察者提供的更新接口。
UML
Java 提供观察者模式的支持
一般在真实项目之中,不会完全手动实现一个观察者模式,因为在 JAVA 语言的 java.util 库里面,已经提供了一个 Observable 类以及一个 Observer 接口,构成 JAVA 语言对观察者模式的支持。直接使用提供的 util 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class ConcreteObserver implements Observer { @Override public void update(Observable o, Object arg) { System.out.println("接收到更新"); } }
public class ConcreteSubject extends Observable {
public void change() { setChanged(); this.notifyObservers(); } }
public class Test { public static void main(String[] args) { ConcreteObserver concreteObserver = new ConcreteObserver(); ConcreteSubject subject = new ConcreteSubject(); subject.addObserver(concreteObserver); subject.change(); } }
|
实战
除了 Java 自身 提供的观察者模式支持外,Guava 也基于观察者模式实现的 生产/消费模型,在使用上,比 Observable 相对简单,如果需要订阅消息只需要在方法上添加 @Subscribe 注解即可。使用 EventBus 的 post 方法分发事件给消费者。
本文以用户注册为例,当用户注册完成后,之后可能会有一系列的耗时操作,比如发送消息通知,同步数据到缓存等操作。为了给用户提供好的体验,这里使用 EventBus 来进行异步化。
定义抽象主题
| public abstract class AbstractProducer<T> { public static final AsyncEventBus eventBus = new AsyncEventBus("_event_async_", Executors.newFixedThreadPool(4));
public void registerAsyncEvent(EventConsumer consumer) { eventBus.register(consumer); }
public abstract void post(T event); }
|
定义抽象观察者
| public interface EventConsumer<T> { void consume(T event); }
|
封装 Event 事件对象
Event 事件对象用于事件宣发时参数传递使用。
| @Data public class UserRegisterEvent { private UserDto userDto; }
@Data @ToString @AllArgsConstructor public class UserDto { private Long id; private String name; private LocalDateTime registerTime; }
|
定义具体的主题与观察者对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| @Component public class UserRegisterProducer extends AbstractProducer<UserRegisterEvent> {
@Override public void post(UserRegisterEvent event) { eventBus.post(event); } }
@Component public class UserRegisterNotifyConsumer implements EventConsumer<UserRegisterEvent>, InitializingBean {
@Resource private UserRegisterProducer userRegisterProducer;
@Override @Subscribe public void consume(UserRegisterEvent event) { System.out.println("接收到用户注册事件,开始推送通知"); System.out.println(event); System.out.println("接收到用户注册事件,通知推送完毕"); }
@Override public void afterPropertiesSet() { userRegisterProducer.registerAsyncEvent(this); } }
@Component public class UserRegisterSyncCacheConsumer implements EventConsumer<UserRegisterEvent>, InitializingBean {
@Resource private UserRegisterProducer userRegisterProducer;
@Override public void afterPropertiesSet() { userRegisterProducer.registerAsyncEvent(this); }
@Override @Subscribe public void consume(UserRegisterEvent event) { System.out.println("接收到用户注册事件,开始同步 Cache"); System.out.println(event.getUserDto()); System.out.println("接收到用户注册事件,同步 Cache 完毕"); } }
|
注意,观察者对象需要在类进行实例化的时候,进行注册事件,所以实现了 InitializingBean 接口,监听事件消息,需要在监听方法上添加 @Subscribe 注解。
测试
| @RunWith(SpringRunner.class) @SpringBootTest public class UserRegisterProducerTest {
@Resource private UserRegisterProducer userRegisterProducer; @Test public void post() { UserRegisterEvent event = new UserRegisterEvent(); event.setUserDto(new UserDto(1L, "张三", LocalDateTime.now())); userRegisterProducer.post(event); } }
|
结果输出:
| 接收到用户注册事件,开始推送通知 接收到用户注册事件,开始同步 Cache UserDto(id=1, name=张三, registerTime=2020-03-05T22:12:26.386) 接收到用户注册事件,同步 Cache 完毕 UserRegisterEvent(userDto=UserDto(id=1, name=张三, registerTime=2020-03-05T22:12:26.386)) 接收到用户注册事件,通知推送完毕
|
因为观察者模式是无法控制消费顺序的,可能每次的输出结果都是不一致的。
示例代码
参考