前言:设计模式源于生活
观察者基本概念
观察者模式,又可以称之为发布-订阅模式,观察者,顾名思义,就是一个监听者,类似监听器的存在,一旦被观察/监听的目标发生的情况,就会被监听者发现,这么想来目标发生情况到观察者知道情况,其实是由目标将情况发送到观察者的 白话文:当一个对象发生改变的时候,可以通知其他所有对象
概念很清晰,举个栗子来理解一下观察者模式的含义,我们都在抖音关注了某位大咖的时候,每当这位大咖更新了一条动态时候,关注大咖的粉丝都能收到通知,简单用一张图来表明他们之间的关系
上面这位大咖发布了个动态,然后他的粉丝都收到通知,并知晓了,从这个栗子可以看到,这里包含两类人,一是大咖,二是粉丝,那么翻译到程序中语言就是观察者的主题和观察者
那么大咖就相当于主题,粉丝相当于观察者,随时观察大咖的动态消息,不过大咖也有权力拉黑你或者让你关注,那么从类图的角度了解一下
observer:抽象观察者,是观察者者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。这就是我们所有粉丝的抽象 ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。具体每一个粉丝 Subject:抽象主题,他把所有观察者对象保存在一个集合里,可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。意思就是大咖把所有的粉丝都保存在一个账号里面,粉丝数量不限,可以新增粉丝也可以拉黑粉丝 ConcreteSubject:具体主题,该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。意思是我们的大咖一有动态,就会把消息给粉丝。
观察者应用场景
1.对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变 2.对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节 例如: 1.分布式配置中心,当配置发生改变,通过事件监听来刷新配置,常见的有,apollo,nacos,spring config 2.zk的节点,当节点发生变化,会通知所有的客户端 3.多渠道群发,当你关注了某位大咖,那么每次更新动态的时候,所有关注大咖的人,都会收到大咖的动态更新消息提示
我这里通过三种方式,来实现异步多渠道群发框架
三种形式的maven依赖都是一样的
代码语言:javascript复制<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<!-- <scope>provided</scope>-->
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
第一种方式,基于java内存的形式实现
抽象观察者
代码语言:javascript复制public interface ObServer {
/**
* 发送消息
*
* @param msg
*/
void sendMsg(String msg);
}
短信具体观察者
代码语言:javascript复制@Slf4j
public class SmsObServer implements ObServer {
@Override
public void sendMsg(String msg) {
log.info("发送短信消息,内容:{}", msg);
}
}
邮件具体观察者
代码语言:javascript复制@Slf4j
public class EmailObServer implements ObServer {
@Override
public void sendMsg(String msg) {
log.info("发送邮件消息,内容:{}", msg);
}
}
抽象主题
代码语言:javascript复制@Slf4j
public abstract class SunnySubject {
protected List<ObServer> obServerList = Lists.newArrayList();
/**
* 注册观察者
*
* @param obServer
*/
public void addObServer(ObServer obServer) {
obServerList.add(obServer);
}
/**
* 移除观察者
*
* @param obServer
*/
public void removeObServer(ObServer obServer) {
boolean contains = obServerList.contains(obServer);
if (contains) {
obServerList.remove(obServer);
}
}
/**
* 通知观察者
*/
public abstract void notifyObServer(String msg);
}
具体主题
代码语言:javascript复制@Slf4j
public class ConcreteSubject extends SunnySubject {
private ExecutorService executorService;
public ConcreteSubject() {
executorService = Executors.newFixedThreadPool(10);
}
@Override
public void notifyObServer(String msg) {
log.info("目标对象状态已变化......发送通知给观察者中");
for (ObServer ob : obServerList) {
executorService.execute(new Runnable() {
@Override
public void run() {
ob.sendMsg(msg);
}
});
}
}
}
启动
代码语言:javascript复制public class Test {
public static void main(String[] args) {
//初始化哦
SunnySubject sunnySubject = new ConcreteSubject();
//注册
sunnySubject.addObServer(new SmsObServer());
sunnySubject.addObServer(new EmailObServer());
//通知
sunnySubject.notifyObServer("你好,观察者");
}
}
第二种方式,基于SpringIOC容器形式实现
抽象观察者
代码语言:javascript复制public interface ObServer {
/**
* 发送消息
*
* @param msg
*/
void sendMsg(String msg);
}
短信具体观察者
代码语言:javascript复制@Component
@Slf4j
public class SmsObServer implements ObServer {
@Override
public void sendMsg(String msg) {
log.info("发送短信消息,内容:{}", msg);
}
}
邮件具体观察者
代码语言:javascript复制@Component
@Slf4j
public class EmailObServer implements ObServer {
@Override
public void sendMsg(String msg) {
log.info("发送邮件消息,内容:{}", msg);
}
}
抽象主题
代码语言:javascript复制@Slf4j
public abstract class SunnySubject {
protected List<ObServer> obServerList = Lists.newArrayList();
/**
* 注册观察者
*
* @param obServer
*/
public void addObServer(ObServer obServer) {
obServerList.add(obServer);
}
/**
* 移除观察者
*
* @param obServer
*/
public void removeObServer(ObServer obServer) {
boolean contains = obServerList.contains(obServer);
if (contains) {
obServerList.remove(obServer);
}
}
/**
* 通知观察者
*/
public abstract void notifyObServer(String msg);
}
具体主题
代码语言:javascript复制@Component
@Slf4j
public class ConcreteSubject extends SunnySubject {
private ExecutorService executorService;
public ConcreteSubject() {
executorService = Executors.newFixedThreadPool(10);
}
@Override
public void notifyObServer(String msg) {
log.info("目标对象状态已变化......发送通知给观察者中");
for (ObServer ob : obServerList) {
executorService.execute(new Runnable() {
@Override
public void run() {
ob.sendMsg(msg);
}
});
}
}
}
观察者配置类
代码语言:javascript复制@Component
@Slf4j
public class ObServerConfig implements ApplicationRunner {
@Autowired
private SmsObServer smsObServer;
@Autowired
private EmailObServer emailObServer;
@Autowired
private ConcreteSubject concreteSubject;
@Override
public void run(ApplicationArguments args) throws Exception {
concreteSubject.addObServer(smsObServer);
concreteSubject.addObServer(emailObServer);
}
}
入口
代码语言:javascript复制@RestController
public class ObServerController {
@Autowired
private ConcreteSubject concreteSubject;
@GetMapping("/send")
public void test(){
concreteSubject.notifyObServer("你好,观察者");
}
}
访问:http://localhost:8080/send
当然,如上面我们的代码还可以在优化一下,实现动态注册
代码语言:javascript复制@Component
@Slf4j
public class ObServerConfig implements ApplicationRunner, ApplicationContextAware {
@Autowired
private SmsObServer smsObServer;
@Autowired
private EmailObServer emailObServer;
@Autowired
private ConcreteSubject concreteSubject;
private ApplicationContext applicationContext;
@Override
public void run(ApplicationArguments args) throws Exception {
// concreteSubject.addObServer(smsObServer);
// concreteSubject.addObServer(emailObServer);
Map<String, ObServer> map = applicationContext.getBeansOfType(ObServer.class);
for(String key : map.keySet()){
ObServer observer = map.get(key);
concreteSubject.addObServer(observer);
}
}
/**
* 获取上下文环境对象得到Spring容器中的Bean
*
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
第三种方式,基于Spring事件形式实现
实体类
代码语言:javascript复制public class UserMessageEntity extends ApplicationEvent {
private String email;
private Long phone;
private Long userId;
public UserMessageEntity(Object source) {
super(source);
}
public UserMessageEntity(Object source, String email, Long phone) {
super(source);
this.email = email;
this.phone = phone;
}
@Override
public String toString() {
return "UserMessageEntity{"
"email='" email '''
", phone=" phone
", userId=" userId
'}';
}
}
邮箱事件回调通知
代码语言:javascript复制@Component
@Slf4j
public class EmailListener implements ApplicationListener<UserMessageEntity> {
@Override
@Async
public void onApplicationEvent(UserMessageEntity userMessageEntity) {
log.info("邮箱通知:{}", userMessageEntity.toString());
}
}
短信事件回调通知
代码语言:javascript复制@Component
@Slf4j
public class SmsListener implements ApplicationListener<UserMessageEntity> {
@Override
public void onApplicationEvent(UserMessageEntity userMessageEntity) {
log.info("短信通知:{}", userMessageEntity.toString());
}
}
入口
代码语言:javascript复制@RestController
public class ObServerController {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@GetMapping("/send")
public void sendTwo() {
UserMessageEntity userMessageEntity = new UserMessageEntity(this, "123456@163.com", 15096111111L);
applicationEventPublisher.publishEvent(userMessageEntity);
}
}
访问:http://localhost:8080/send
总结
观察者模式的主要优点在于可以实现表示层和数据逻辑层的分离,并在观察目标和观察者之间建立一个抽象的耦合,支持广播通信;其主要缺点在于如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间,而且如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 其实还有一点需要我们去了解,在上面的例子当中我们的会发现,其实粉丝的消息是大咖推过来的,还有一种观察者模式,也就是我们的粉丝主动去获取消息。 (1)推模型: 主题对象向观察者推送主题的详细信息,不管是否需要。 (2)拉模型:主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取