0
点赞
收藏
分享

微信扫一扫

五分钟学会观察者模式


五分钟学会观察者模式_spring

介绍

观察者模式:多个观察者同时监听一个主题对象,当主题对象发生改变时,它的所有观察者都会收到通知。

例如微信公众号,当作者发文时,所有的订阅者都会收到。这样观察者模式就能实现广播,同时符合开闭原则,增加新的观察者不用改原有的代码。

观察者模式的UML图如下

五分钟学会观察者模式_spring_02


Subject(主题):定义了观察者集合,提供注册,删除,通知观察者的方法

ConcreteSubject(具体主题)

Observer(观察者):定义了收到主题通知时所做的改变

ConcreteObserver(具体观察者)

手写观察者模式

以微信公众号为例写一个观察者模式

抽象主题

public interface MySubject {

void registerObserver(MyObserver o);

void removeObserver(MyObserver o);

void notifyObserver();
}

抽象观察者

public interface MyObserver {
void update(String authorName, String articleName);
}

具体主题

public class WeChatServer implements MySubject {

private List<MyObserver> myObservers;
private String authorName;
private String articleName;

public WeChatServer(String authorName) {
myObservers = new ArrayList<>();
this.authorName = authorName;
}

public void publishArticle(String articleName) {
this.articleName = articleName;
notifyObserver();
}

@Override
public void registerObserver(MyObserver o) {
myObservers.add(o);
}

@Override
public void removeObserver(MyObserver o) {
if (myObservers.contains(o)) {
myObservers.remove(o);
}
}

@Override
public void notifyObserver() {
myObservers.forEach(item -> {
item.update(authorName, articleName);
});
}
}

具体观察者

public class WeChatClient implements MyObserver {

private String username;

public WeChatClient(String username) {
this.username = username;
}

@Override
public void update(String authorName, String articleName) {
System.out.println(username + ": " + authorName + " 发了一篇文章 " + articleName);
}
}

测试类

public class Main {

public static void main(String[] args) {
WeChatServer weChatServer = new WeChatServer("Java识堂");
WeChatClient user1 = new WeChatClient("张三");
WeChatClient user2 = new WeChatClient("李四");
weChatServer.registerObserver(user1);
weChatServer.registerObserver(user2);
weChatServer.publishArticle("《五分钟学会观察者模式》");
}
}

输出为

张三: Java识堂 发了一篇文章 《五分钟学会观察者模式》
李四: Java识堂 发了一篇文章 《五分钟学会观察者模式》

观察者模式的应用

JDK提供了观察者模式接口

Java在java.util包中有对观察者模式进行支持,它定义了2个接口

抽象观察者

public interface Observer {
void update(Observable o, Object arg);
}

抽象主题

public class Observable {
private boolean changed = false;
private Vector<Observer> obs;

public Observable() {
obs = new Vector<>();
}

public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}

public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}

public void notifyObservers() {
notifyObservers(null);
}

public void notifyObservers(Object arg) {

Object[] arrLocal;

synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}

for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}

public synchronized void deleteObservers() {
obs.removeAllElements();
}

protected synchronized void setChanged() {
changed = true;
}

protected synchronized void clearChanged() {
changed = false;
}

public synchronized boolean hasChanged() {
return changed;
}

public synchronized int countObservers() {
return obs.size();
}
}

和我们之前定义的很相似哈,只是多了一个change的开关字段,并且保证了线程安全

我们来把之前的例子改写一下,定义事件对象

@Data
@AllArgsConstructor
public class NewArticleEvent {

private String authorName;
private String articleName;
}

public class WeChatServer extends Observable {

private String authorName;
private String articleName;

public WeChatServer(String authorName) {
this.authorName = authorName;
}

public void publishArticle(String articleName) {
setChanged();
this.articleName = articleName;
notifyObservers(new NewArticleEvent(authorName, articleName));
}
}

public class WeChatClient implements Observer {

private String username;

public WeChatClient(String username) {
this.username = username;
}

@Override
public void update(Observable o, Object arg) {
NewArticleEvent event = (NewArticleEvent) arg;
System.out.println(username + ": " + event.getAuthorName() + " 发了一篇文章 " + event.getAuthorName());
}
}

public class Main {

public static void main(String[] args) {
WeChatServer weChatServer = new WeChatServer("Java识堂");
WeChatClient user1 = new WeChatClient("张三");
WeChatClient user2 = new WeChatClient("李四");
weChatServer.addObserver(user1);
weChatServer.addObserver(user2);
weChatServer.publishArticle("《五分钟学会观察者模式》");
}
}

输出和上面一样

在spring中自定义事件

Spring用观察者模式来实现事件监听,在spring中实现事件监听比较简单,还是把上面的例子改造一下

事件类型需要继承ApplicationEvent类

@Data
public class NewArticleEvent extends ApplicationEvent {

private String authorName;
private String articleName;

public NewArticleEvent(Object source, String authorName, String articleName) {
super(source);
this.authorName = authorName;
this.articleName = articleName;
}

}

我们可以通过实现ApplicationListener接口或者使用@EventListener注解来实现事件监听

@Component
public class NewArticleEventListener implements ApplicationListener<NewArticleEvent> {

@Override
public void onApplicationEvent(NewArticleEvent event) {
System.out.println(event.getAuthorName() + " 发了一篇文章 " + event.getArticleName());
}
}

@Component
public class MyEventListener {

@EventListener
public void newArticleEventListener(NewArticleEvent event) {
System.out.println(event.getAuthorName() + " 发了一篇文章 " + event.getArticleName());
}
}

上面的例子用两种方式写了2个观察者,下面开始写测试类。上面的类都在com.javashitang.part6包下哈

@Configuration
@ComponentScan("com.javashitang.part6")
public class AppConfig {
}

public class Main {

public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
context.publishEvent(new NewArticleEvent(context, "Jva识堂", "《五分钟学会观察者模式》"));
context.close();
}
}

此时可以看到控制台输出

Jva识堂 发了一篇文章 《五分钟学会观察者模式》
Jva识堂 发了一篇文章 《五分钟学会观察者模式》

好了,到现在你已经会在spring中自定义事件了。spring中观察者模式的实现可能复杂一点,但基本思想就是上面提到的。

  • ApplicationEvent是Spring的事件接口
  • ApplicationListener是Spring的事件监听器接口,所有的监听器都实现该接口
  • ApplicationEventPublisher是Spring的事件发布接口,ApplicationContext实现了该接口

spring提供了一些ApplicationEvent的实现类供我们使用

  • ContextStartedEvent:ApplicationContext容器在初始化的时候发布的事件类型
  • ContextClosedEvent:ApplicationContext容器在即将关闭的时候发布的事件类型
  • ContextRefreshedEvent:ApplicationContext容器在初始化或者刷新的时候发布的事件类型

如果你想知道spring定义好的事件在项目中的应用。你可以看我之前的文章,看我们是如何利用注解+spring事件来实现策略模式的

项目中的if else太多了,该怎么重构?

自定义事件在spring-cloud-eureka中的应用

我们都知道eureka是一个注册中心,保存了服务名->具体的ip地址的关系,它是用
ConcurrentHashMap来保存这种映射关系的。既然是注册中心肯定涉及到服务的注册和取消,eureka在register(注册),cancel(取消),renew(续约)发生时,都会发布一个事件,如下代码所示

InstanceRegistry.java

private void handleRegistration(InstanceInfo info, int leaseDuration,
boolean isReplication) {
log("register " + info.getAppName() + ", vip " + info.getVIPAddress()
+ ", leaseDuration " + leaseDuration + ", isReplication "
+ isReplication);
publishEvent(new EurekaInstanceRegisteredEvent(this, info, leaseDuration,
isReplication));
}

如果你对服务信息比较感兴趣,如想对数据进行持久化,就可以监听这些事件,是不是可扩展性特别好?

参考博客

spring boot整体流程,有事件部分
[2]https://www.jianshu.com/p/83693d3d0a65
[3]https://blog.lqdev.cn/2018/11/06/springboot/chapter-thirty-two/


举报

相关推荐

0 条评论