设计模式之观察者模式实战
本文原文链接地址:http://nullpointer.pw/design-patterns-observer.html
本文介绍观察者模式在项目中的实际应用。
类型:行为型模式
意图:一对多关系依赖的多个对象,当一个对象状态发生改变,所有依赖的对象都可以得到通知并自动更新
主要解决:降低对象间的关联依赖性
观察者模式也称为发布订阅模式,监听器模式。
角色
抽象主题(Subject)角色:抽象主题角色提供维护一个观察者对象聚集的操作方法,对聚集的增加、删除等。
具体主题(ConcreteSubject)角色:将有关状态存入具体的观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色负责实现抽象主题中聚集的管理方法。
抽象观察者(Observer)角色:为具体观察者提供一个更新接口。
具体观察者(ConcreteObserver)角色:存储与主题相关的自洽状态,实现抽象观察者提供的更新接口。
UML
Java 提供观察者模式的支持
一般在真实项目之中,不会完全手动实现一个观察者模式,因为在 JAVA 语言的 java.util 库里面,已经提供了一个 Observable 类以及一个 Observer 接口,构成 JAVA 语言对观察者模式的支持。直接使用提供的 util 即可。
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;
}
定义具体的主题与观察者对象
// 主题对象,提供注册主题方法
@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))
接收到用户注册事件,通知推送完毕
因为观察者模式是无法控制消费顺序的,可能每次的输出结果都是不一致的。