设计模式 - 观察者模式(发布-订阅模式 or 模型-视图模式)
观察者模式(发布-订阅模式 or 模型-视图模式):被观察(触发器)对象状态改变时,则依赖于它的对象都会被通知并做出反应。
核心:依赖关系注册(一对多),事件产生时,发布人(主动)将事件通知到订阅人(被动)
角色:
- 观察者(订阅人/被通知人):观察者(被动)接受到事件消息后,将做出反应。
- 被观察者(发布人/通知者 - 触发器):定义通知事件,当被观察者的业务逻辑被执行时,会(主动)将对应事件发送给观察者
- 订阅关系容器:维护事件与观察者之间的(一对多)联系(注册制),可能被集成于被观察者中,或者独立成类(此时,被观察者只负责产生事件,而由容器完成事件通知)
general(订阅关系由被观察者维护)
Observer
// 观察者,提供一个被通知方法(update - 回调方法)
public interface IObserver<E> {
void update(E event);
}
public class Observer<E> implements IObserver<E>{
@Override
public void update(E event) {
System.out.println("Observer:receive message : " + event);
}
}
Subject
// 被观察者,提供注册和注销功能(维护该事件的观察者列表),以及事件触发方法notify,在notify中通知所有观察者。
public interface ISubject<E> {
boolean register(IObserver<E> observer);
boolean deregister(IObserver<E> observer);
void notify(E event);
}
public class Subject<E> implements ISubject<E>{
private ConcurrentLinkedQueue<IObserver> observers = new ConcurrentLinkedQueue<>();
@Override
public boolean register(IObserver<E> observer) { return observers.add(observer);}
@Override
public boolean deregister(IObserver<E> observer) { return observers.remove(observer);}
@Override
public void notify(E event) {
IObserver[] iObservers = observers.toArray(new IObserver[observers.size()]);
for(IObserver iObserver : iObservers){
iObserver.update(event);
}
}
}
// 观察者模式(发布订阅模式):Observer需在Subject对象内进行注册,当发布消息时,Subject将消息提交给Observer。
// Subject对象中发布消息的功能,与维护Observer列表相耦合,可进一步拆分,实现单一职责。
public class Test {
public static void main(String[] args) {
Observer<String> observerA = new Observer<>();
Observer<String> observerB = new Observer<>();
Subject<String> subject = new Subject<>();
subject.register(observerA);
subject.register(observerB);
subject.notify("字符串类型消息");
}
}
publish/subscribe(发布和订阅关系的维护以及通知职责独立成context对象)
// 职责1:维护订阅关系。
// 职责2:接受到事件消息时,完成分发
public class Context {
private ConcurrentHashMap<String, ConcurrentLinkedQueue<IUser>> subscribeMap = new ConcurrentHashMap<>();
// 订阅
public boolean subscribe(String publishUserId, IUser subscribeUser){
if(!subscribeMap.containsKey(publishUserId)) {
subscribeMap.putIfAbsent(publishUserId, new ConcurrentLinkedQueue<IUser>());
}
return subscribeMap.get(publishUserId).add(subscribeUser);
}
public boolean unsubscribe(String publishUserId, IUser subscribeUser){
return subscribeMap.get(publishUserId).remove(subscribeUser);
}
// 分发
public void publish(String userId, String message) {
IUser[] iUsers = subscribeMap.get(userId).toArray(new IUser[subscribeMap.get(userId).size()]);
for(IUser iUser : iUsers){
iUser.subscribe(userId, message);
}
}
}
// 用户对象,只与context进行交流。
public class User {
private String userId;
private Context context;
public User(String userId, Context context){
this.userId = userId;
this.context = context;
}
// 发布信息
public void publish(String message){
context.publish(userId,message);
}
// 订阅消息通知入口
public void subscribe(String userId, String message) {
System.out.println(this.userId + ": message from " + userId + " - " + message);
}
}
Spring中集成的发布订阅模式
// 条件①:作为springbean对象被注册到applicationContext。
// 条件②:实现ApplicationListener<T extends ApplicationEvent>方法
// --- 即可作为观察者注册到applicationContext中,观察事件即为T对象事件
@Component
public class MyListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent myEvent) {
System.out.println("MyListener - " + myEvent.getMessage());
}
}
public class MyEvent extends ApplicationEvent {
private String message;
public MyEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() { return message;}
public void setMessage(String message) { this.message = message;}
}
// Spring提供的事件监听(发布订阅)模式 - 采用SpringBootTest进行测试
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MySpringBootApplication.class)
public class MySpringBootApplicationTest {
@Autowired
private ApplicationContext applicationContext;
@Test
public void test(){
// 定义事件,并将事件发布到applicationContext中。
// 具体的监听者在springBoot启动时,所有实现ApplicationListener<MyEvent>接口的spring bean对象均会注册到ApplicationEventMulticaster中,根据事件类型被通知调用
MyEvent order = new MyEvent(this, "kiqi的消息");
applicationContext.publishEvent(order);
}
}
欢迎疑问、期待评论、感谢指点 -- kiqi,愿同您为友
-- 星河有灿灿,愿与之辉