浅析观察者模式在Java中的应用

简介: 观察者模式(Observer Design Pattern),也叫做发布订阅模式(Publish-Subscribe Design Pattern)、模型-视图(Model-View)模式、源-监听器(Source-Listener)模式、从属者(Dependents)模式
观察者模式(Observer Design Pattern),也叫做发布订阅模式(Publish-Subscribe Design Pattern)、模型-视图(Model-View)模式、源-监听器(Source-Listener)模式、从属者(Dependents)模式

观察者模式应用最广的场景就是消息发布订阅, 比如当系统中完成一个业务事件, 需要通知给不同的用户平台,这个时候就会有多种通知方式,如:

image.png

那么就需要将消息发布对象这一个状态的改变, 通知给另外多个平台的对象,像这种情况就适合观察者模式。下面就详细的介绍一下观察者模式

一、观察者模式的介绍

前面说到,观察者是一种行为设计模式,允许一个对象将其状态的改变通知其他对象。实际上主要的部分就是观察者和被观察者,比如前言提到的消息发布,就属于被观察者,而各种不同的平台消息提醒,则是一系列的观察者。那么观察者如何获取被观察者的状态变化呢?在观察者模式中是这样设计的:

1.1 观察者模式的结构

在被观察者这一块,通过在被观察者中管理观察者,如add() 增加观察者,remove() 移除观察者和notifyObserver() 通知/发送消息给观察者等等方法,来达到通知观察者,管理观察者数量的作用。在观察者这个角度,需要有response() 接收被观察者通知的方法,来完成整个观察者模式的闭环。用UML图来展示则如下所示:

观察者模式结构中主要包括被观察者(Object)和观察者(Observer)两个结构:

image-20220409112301928

  • Subject:主题抽象类,提供一系列观察者对象,以及对这些对象的增加、删除和通知的方法
  • ConcreteSubject:主题具体实现类,实现抽象主题中的通知方法,通知所有注册过的观察者对象
  • Observer:观察者抽象类,包含一个通知响应抽象方法
  • ConcreteObserver1、ConcreteObserver2:观察者实现类,实现抽象观察者中的方法,以便在得到目标的更改通知时更新自身的状态
  • Client:客户端,对主题及观察者进行调用

从上面的UML图我们会知道,通过抽象观察者和被观察者,然后分别实现不同的观察者和被观察者,来达到将多个对象的不同状态传递给多个对象的作用。

1.2 观察者模式的实现

根据上面的类图,我们可以实现对应的代码。

首先定义一个抽象目标类Subject,其中包括增加、注销和通知观察者方法

public abstract class Subject {

    protected List<Observer> observerList = new ArrayList<Observer>();

    /**
     * 增加观察者
     * @param observer 观察者
     */
    public void add(Observer observer) {
        observerList.add(observer);
    }

    /**
     * 注销观察者,从观察者集合中删除一个观察者
     * @param observer 观察者
     */
    public void remove(Observer observer) {
        observerList.remove(observer);
    }

    /**通知观察者*/
    public abstract void notifyObserver();
}
AI 代码解读

对应具体的目标类ConcreteSubject

public class ConcreteSubject extends Subject{

    @Override
    public void notifyObserver() {
        System.out.println("遍历观察者:");
        for (Observer observer : observerList) {
            observer.response();
        }
    }
}
AI 代码解读

此外需要定义抽象观察者Observer,它一般定义为一个接口,声明一个response()方法,为不同观察者的响应行为定义相同的接口:

public interface Observer {
    /**声明响应方法*/
    void response();
}
AI 代码解读

具体的观察者实现:

public class ConcreteObserver1 implements Observer{

    @Override
    public void response() {
        System.out.println("我是具体观察者ConcreteObserver1");
    }
}

public class ConcreteObserver2 implements Observer{

    @Override
    public void response() {
        System.out.println("我是具体观察者ConcreteObserver2");
    }
}
AI 代码解读

最后是客户端测试:

public class Client {
    public static void main(String[] args) {
        Subject concreteSubject = new ConcreteSubject();
        //具体观察者
        Observer concreteObserver1 = new ConcreteObserver1();
        Observer concreteObserver2 = new ConcreteObserver2();
        concreteSubject.add(concreteObserver1);
        concreteSubject.add(concreteObserver2);
        
        concreteSubject.notifyObserver();
    }
}
AI 代码解读

测试结果:

遍历观察者:
我是具体观察者ConcreteObserver1
我是具体观察者ConcreteObserver2
AI 代码解读

1.3 观察者模式和订阅发布者模式的区别

在平常的开发中, 我们也会接触到订阅发布者模式(Pub-Sub Pattern), 前面我们也说到它是观察者模式另外一种叫法,但是它们两者之间有什么不同呢?先来看看两个模式的实现结构:

在实际业务中,发布订阅者模式就是消息中间件的实现, 两者的区别在于:观察者模式中, 被观察者和观察者两者直接相关依赖,而在发布订阅者模式中,因为有调度中心的存在,被观察者和观察者两者不相互依赖,因此对于发布订阅者模式,实现了被观察者和观察者的解耦,更利于各个组件的独立扩展。

在具体调度中心中,可以通过不同的消息中间件来完成,比如Java中的RabbitMQ, Kafka等等

二、观察者模式的应用场景

在以下情况就可以考虑使用观察者模式:

  1. 一个对象的改变会导致一个或多个对象发生改变,而并不知道具体有多少对象将会发生改变,也不知道这些对象是谁
  2. 当一个抽象模型有两个方面,其中的一个方面依赖于另一个方面时,可将这两者封装在独立的对象中以使他们可以各自独立地改变和复用
  3. 需要在系统中创建一个触发链,使得事件拥有跨域通知(跨越两种观察者的类型)

2.1 观察者模式在java.util包中的应用

观察者模式在JDK中就有典型应用,比如java.util.Observablejava.util.Observer类。结构如下图所示:

image-20220409083948434

我们可以通过实现具体的ConcreteObserver和具体的ConcreteObservable完成观察者模式流程

2.2 观察者模式在MVC中的应用

MVC(Modew-View-Controller)架构中也应用了观察者模式,其中模型(Model)可以对应观察者模式中的观察目标,而视图(View)对应于观察者,控制器(Controller)就是中介者模式的应用:

image-20220409091533004

三、观察者模式实战

3.1 在本案例中模拟北京小客车指标摇号事件的消息通知场景(来源于《重学Java设计模式》)

对于通知事件,可以将其分成三个部分:事件监听事件处理具体的业务流程,如下图所示:

image-20220409095032686

对于和核心流程和非核心流程的结构,非核心流程可以是异步的,在MQ以及定时任务的处理下,能够最终保证一致性。

3.1.1 具体代码实现

  1. 事件监听接口及具体实现

这个部分就相当于观察者(Observer)的角色, 需要被观察者将信息发送给这些不同的观察者

在接口中定义基本事件类方法doEvent(),相当于UML图中的response() 方法

public interface EventListener {

    void doEvent(LotteryResult result);

}
AI 代码解读

监听事件的具体实现MessageEventListener(短消息事件)和MQEventListener(MQ发送事件)

public class MessageEventListener implements EventListener{

    private Logger logger = LoggerFactory.getLogger(MessageEventListener.class);

    @Override
    public void doEvent(LotteryResult result) {
        logger.info("给用户 {} 发送短信通知(短信):{}", result.getuId(), result.getMsg());
    }
}

public class MQEventListener implements EventListener{

    private Logger logger = LoggerFactory.getLogger(MQEventListener.class);

    @Override
    public void doEvent(LotteryResult result) {
        logger.info("记录用户 {} 摇号结果(MQ):{}", result.getuId(), result.getMsg());
    }
}
AI 代码解读
  1. 事件处理类

该部分就相当于Object部分,也就是被观察者

对于不同的事件类型(MQ和Message)进行枚举处理,并提供三个方法:subscribe()unsubscribe()notify()用于对监听事件的注册和使用:

public class EventManager {

    Map<Enum<EventType>, List<EventListener>> listeners = new HashMap<>();

    public EventManager(Enum<EventType>... operations) {
        for (Enum<EventType> operation : operations) {
            listeners.put(operation, new ArrayList<>());
        }
    }

    public enum EventType {
        MQ,
        Message
    }

    /**
     * 订阅
     * @param eventType 事件类型
     * @param listener  监听
     */
    public void subscribe(Enum<EventType> eventType, EventListener listener) {
        List<EventListener> eventListeners = listeners.get(eventType);
        eventListeners.add(listener);
    }

    /**
     * 取消订阅
     * @param eventType 事件类型
     * @param listener 监听
     */
    public void unsubscribe(Enum<EventType> eventType, EventListener listener) {
        List<EventListener> eventListeners = listeners.get(eventType);
        eventListeners.remove(listener);
    }

    /**
     * 通知
     * @param eventType 事件类型
     * @param result    结果
     */
    public void notify(Enum<EventType> eventType, LotteryResult result) {
        List<EventListener> eventListeners = listeners.get(eventType);
        for (EventListener eventListener : eventListeners) {
            eventListener.doEvent(result);
        }
    }
}
AI 代码解读
  1. 业务抽象类接口及其实现

这一部分不属于观察者模式中的部分,是属于具体业务中的代码, 通过使用抽象类的方式实现方法可以在方法中扩展额外的调用,并提供抽象方法doDraw(),让继承者去实现具体逻辑

public abstract class LotteryService {

    private EventManager eventManager;

    public LotteryService() {
        eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);
        eventManager.subscribe(EventManager.EventType.MQ, new MQEventListener());
        eventManager.subscribe(EventManager.EventType.Message, new MessageEventListener());
    }

    public LotteryResult draw(String uId) {
        LotteryResult lotteryResult = doDraw(uId);
        eventManager.notify(EventManager.EventType.MQ, lotteryResult);
        eventManager.notify(EventManager.EventType.Message, lotteryResult);
        return lotteryResult;
    }

    protected abstract LotteryResult doDraw(String uId);
}

public class LotteryServiceImpl extends LotteryService{

    private MinibusTargetService minibusTargetService = new MinibusTargetService();

    @Override
    protected LotteryResult doDraw(String uId) {
        //摇号测试
        String lottery = minibusTargetService.lottery(uId);
        return new LotteryResult(uId, lottery, new Date());
    }
}
AI 代码解读
  1. 其他的类

摇号服务接口:

/**
 * 小客车指标调控服务
 */
public class MinibusTargetService {

    /**
     * 模拟摇号,但不是摇号算法
     *
     * @param uId 用户编号
     * @return 结果
     */
    public String lottery(String uId) {
        return Math.abs(uId.hashCode()) % 2 == 0 ? "恭喜你,编码".concat(uId).concat("在本次摇号中签") : "很遗憾,编码".concat(uId).concat("在本次摇号未中签或摇号资格已过期");
    }

}
AI 代码解读

事件信息返回类:用于返回信息,在实际业务中这一部分可以进行对应的取舍

public class LotteryResult {

    private String uId;
    private String msg;
    private Date dateTime;

    //get set constructor... 
}
AI 代码解读
  1. 测试类
public class ApiTest {

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    @Test
    public void test() {
        LotteryServiceImpl lotteryService = new LotteryServiceImpl();
        LotteryResult result = lotteryService.draw("1234567");
        logger.info("摇号结果:{}", JSON.toJSONString(result));
    }
}
AI 代码解读

测试结果:

11:43:09.284 [main] INFO  c.e.d.event.listener.MQEventListener - 记录用户 1234567 摇号结果(MQ):恭喜你,编码1234567在本次摇号中签
11:43:09.288 [main] INFO  c.e.d.e.l.MessageEventListener - 给用户 1234567 发送短信通知(短信):恭喜你,编码1234567在本次摇号中签
11:43:09.431 [main] INFO  ApiTest - 摇号结果:{"dateTime":1649475789279,"msg":"恭喜你,编码1234567在本次摇号中签","uId":"1234567"}
AI 代码解读

3.2 监听系统工作流完成消息,分别发送给不同的平台,比如微信,钉钉等

image.png

3.2.1 具体代码实现

  1. 消息事件监听

该类监听系统中的流程信息,通过这个processor来管理第三方平台,并选择性推送。继承ThirdMessageListener,接收系统传递过来的流程信息userMessage。

@ThirdMessageListenerAnnotation
public class ThirdMessageProcessor implements ThirdMessageListener {

    @Autowired
    private WeChatMessageSender weChatMessageSender;

    @Value("${sys.ThirdPlatform.weChat}")
    private boolean weChatSwitch;

    @Value("${sys.ThirdPlatform.DingDing}")
    private boolean dingDingSwitch;

    @Override
    public void onMessage(FlowUserMessage userMessage) {
        //若在系统配置文件中关闭该三方平台, 则对该平台不发送消息
        if (weChatSwitch || dingDingSwitch) {
            //集成各种第三方平台
            MessageSenderManager messageSenderManager = new MessageSenderManager(MessageSenderManager.MessagePlatform.weChat);
            //将微信平台集成在消息发送管理器中
            messageSenderManager.subscribe(MessageSenderManager.MessagePlatform.weChat, beijingOfficeMessageSender);
            //向微信平台发送消息
            messageSenderManager.notify(MessageSenderManager.MessagePlatform.weChat, userMessage);
        }
    }
}
AI 代码解读
  1. 消息发送管理器

    该部分是MessageSenderManager,相当于观察者模式中的被观察者,对不同的观察者进行管理和消息发送:

public class MessageSenderManager {

    Map<Enum<MessagePlatform>, List<MessageSender>> senders = new HashMap<>();

    public MessageSenderManager(Enum<MessagePlatform>... operations) {
        for (Enum<MessagePlatform> operation : operations) {
            senders.put(operation, new ArrayList<>());
        }
    }

    /**
     * 配置不同平台类型
     */
    public enum MessagePlatform {
        /**
         * 微信平台
         */
        Wechat,
        DingDing
    }

    /**
     * 订阅对应平台的监听器
     * @param platformType 平台类型
     * @param sender 监听器
     */
    public void subscribe(Enum<MessagePlatform> platformType, MessageSender sender) {
        List<MessageSender> messageSenders = senders.get(platformType);
        messageSenders.add(sender);
    }

    /**
     * 取消对应平台的监听器
     * @param platformType 平台类型
     * @param sender 监听器
     */
    public void unsubscribe(Enum<MessagePlatform> platformType, MessageSender sender) {
        List<MessageSender> messageSenders = senders.get(platformType);
        messageSenders.remove(sender);
    }

    /**
     * 通知消息
     * @param platformType 平台类型
     * @param userMessage 监听器
     */
    public void notify(Enum<MessagePlatform> platformType, FlowUserMessage userMessage) {
        List<MessageSender> messageSenders = senders.get(platformType);
        for (MessageSender messageSender : messageSenders) {
           messageSender.sendMessage(userMessage);
        }
    }

}
AI 代码解读
  1. 具体平台消息发送

该部分是具体的平台消息发送过程,也就是观察者,当观察者订阅后,每次被观察状态变化后,都会将该状态发送给观察者。

public interface MessageSender {

    /**
     * 发送消息
     * @param userMessage 流程消息
     */
    void sendMessage(FlowUserMessage userMessage);

    /**
     * 重发消息
     * @param userMessage 流程消息
     */
    void resendMessage(FlowUserMessage userMessage);
}

@Component
public class WeixinMessageSender implements MessageSender{


    @Override
    public void sendMessage(FlowUserMessage userMessage) {
       ...
    }

    @Override
    public void resendMessage(FlowUserMessage userMessage) {
        ...
    }

}
AI 代码解读

四、参考资料

《重学Java设计模式》

《设计模式》

相关实践学习
消息队列RocketMQ版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
目录
打赏
0
3
3
0
17
分享
相关文章
Java也能快速搭建AI应用?一文带你玩转Spring AI可落地性
Java语言凭借其成熟的生态与解决方案,特别是通过 Spring AI 框架,正迅速成为 AI 应用开发的新选择。本文将探讨如何利用 Spring AI Alibaba 构建在线聊天 AI 应用,并实现对其性能的全面可观测性。
Java和Python在企业中的应用情况
Java和Python在企业中的应用情况
120 7
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
291 3
Java 也能快速搭建 AI 应用?一文带你玩转 Spring AI 可观测性
Java 也能快速搭建 AI 应用?一文带你玩转 Spring AI 可观测性
CRaC技术助力ACS上的Java应用启动加速
容器计算服务借助ACS的柔性算力特性并搭配CRaC技术极致地提升Java类应用的启动速度。
Java 也能快速搭建 AI 应用?一文带你玩转 Spring AI 可观测性
Java 也能快速搭建 AI 应用?一文带你玩转 Spring AI 可观测性
Java中的this关键字详解:深入理解与应用
本文深入解析了Java中`this`关键字的多种用法
182 9
【潜意识Java】javaee中的SpringBoot在Java 开发中的应用与详细分析
本文介绍了 Spring Boot 的核心概念和使用场景,并通过一个实战项目演示了如何构建一个简单的 RESTful API。
50 5
【潜意识Java】了解并详细分析Java与AIGC的结合应用和使用方式
本文介绍了如何将Java与AIGC(人工智能生成内容)技术结合,实现智能文本生成。
154 5
【潜意识Java】深入理解MyBatis,从基础到高级的深度细节应用
本文详细介绍了MyBatis,一个轻量级的Java持久化框架。内容涵盖MyBatis的基本概念、配置与环境搭建、基础操作(如创建实体类、Mapper接口及映射文件)以及CRUD操作的实现。此外,还深入探讨了高级特性,包括动态SQL和缓存机制。通过代码示例,帮助开发者更好地掌握MyBatis的使用技巧,提升数据库操作效率。总结部分强调了MyBatis的优势及其在实际开发中的应用价值。
38 1

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等