了解如何使用 Guava 和 Spring 设计观察者模式

文章先发于公众号(龙泰的技术笔记),后同步到掘金和个人网站:

今天我将解释一个行为设计模式,什么是行为设计​​模式?行为类型主要负责设计类或对象之间的交互。工作中常用的观察者模式是行为设计​​模式

我最近尝试重构我之前编写的代码。重新整理业务后,发现现有的设计场景应该可以对接设计模式,并查看了代码提交记录,让这个想法坚定了。

保持以前一贯的风格,想要说明一个设计模式需要三轴支持。什么是观察者模式?如何使用观察者模式?在项目中应该如何应用?

观察者设计模式的大纲如下:

什么是观察者模式观察者模式代码如何编写观察者模式如何结合业务Guava观察者模式事件模型总结什么是观察者模式

观察者模式是一种行为设计模式,它允许定义一个订阅通知机制,当一个对象上发生事件时,可以通知该对象的多个“可观察对象”(观察到的)观察者对象也称为发布-订阅模型.

其实,就个人而言,我不喜欢用文字来定义设计模式的语义,因为它总是很难理解。所以下面有生活中的例子来帮助读者更好地理解模式的语义。类图如下:

在给出示例之前,我们先熟悉一下观察者模式中的角色类型和代码示例。观察者模式由以下角色组成,大家可以参考代码示例来理解,不要被文字描述带偏了

public interface Subject {
    void register(Observer observer);  // 添加观察者
    void remove(Observer observer);  // 移除观察者
    void notify(String message);  // 通知所有观察者事件
}
public class ConcreteSubject implements Subject {
    private static final List observers = new ArrayList();
    @Override
    public void register(Observer observer) { observers.add(observer); }

    @Override
    public void remove(Observer observer) { observers.remove(observer); }
    @Override
    public void notify(String message) { observers.forEach(each -> each.update(message)); }
}

public interface Observer {
    void update(String message);  // String 入参只是举例, 真实业务不会限制
}
public class ConcreteObserverOne implements Observer {
    @Override
    public void update(String message) {
        // 执行 message 逻辑
        System.out.println("接收到被观察者状态变更-1");
    }
}
public class ConcreteObserverTwo implements Observer {
    @Override

图片[1]-了解如何使用 Guava 和 Spring 设计观察者模式-唐朝资源网

public void update(String message) { // 执行 message 逻辑 System.out.println("接收到被观察者状态变更-2"); } }

让我们运行上面的观察者模式示例,如果不出意外,两个观察者的执行逻辑中的日志都会被打印出来。如果是正常的业务逻辑,抽象观察者定义的输入参数是有业务意义的。大家可以对比一下项目中使用的MQ机制。

public class Example {
    public static void main(String[] args) {
        ConcreteSubject subject = new ConcreteSubject();
        subject.register(new ConcreteObserverOne());
        subject.register(new ConcreteObserverTwo());
        subject.notify("被观察者状态改变, 通知所有已注册观察者");
    }
}

观察者模式结合业务

因为公司的业务场景是保密的,所以我们用【新警察故事】的电影情节,稍微篡改一下剧情,模拟我们的观察者模式应用场景

假设:目前我们有三个警察,分别是龙哥、冯哥和老三。他们奉命追查犯罪嫌疑人阿祖。如果发现嫌疑人阿祖动了,龙哥和冯哥将负责抓捕行动,三号动摇派出所。流程图如下:

如果用常规的代码写这个流程,就可以实现需求,穿梭的逻辑可以实现所有需求。不过,如果说接下来的行动,龙哥会让老三跟着他进行抓捕,或者龙哥的队伍会扩大,来老四、老五、老六…

对比观察者模式的角色定义,老的四、old五、老六都是特定的观察者()

如果我们遵循上述假设,我们可以通过“穿梭”方式编写代码有什么问题?如下:

首当其冲,增加了代码的复杂性。实现类或者这个方法函数非常大,因为随着警务人员的扩展,代码块会越来越大

这违反了开闭原则,因为不同警察的任务会经常变化。每个警察的任务都不是一成不变的。比如这次为了抓捕嫌疑人,就让冯哥进行抓捕行动。下一次,可能是疏散人群。每次改变都需要改变“穿梭”吗?代码

我们解决代码责任问题的第一种方法是将大函数拆分为小函数或将大类拆分为小类。但是,开闭原则无法回避,因为随着警务人员(观察员)的增减,势必会面临原有功能的频繁变化

当我们面对这种已知的变化,并且可能会频繁更改不固定的代码时,就需要使用抽象思维进行设计,以保持代码的简洁和可维护性

这里使用Java项目结构编写观察者模式,代码最终推送到仓库。读者可以先拉下仓库,因为里面不仅包含了示例代码,还包含了Guava的观察者模式的实现和仓库地址

首先,在观察者模式下定义观察者角色,分别为抽象观察者。接口和三个具体的观察者实现类。在实际业务中,设计模式会与框架结合,所以示例代码中包含了相关的注解和接口

其次,定义抽象的观察者接口和具体的 实现类。同上,也需要变成一个Bean,由IOC容器管理

至此,一个完整的观察者模式就完成了。不过细心的读者会发现,这样的观察者模式存在一个小问题,这里不再赘述,继续往下看。接下来需要练习一些练习,注册这些观察者,通过观察者触发的事件通知观察者

如何实现开闭原则

p>

看了应用程序的代码,解决了函数体过大的问题。我们通过将整体逻辑拆分为不同的特定观察者类来拆分整体逻辑。但是开闭原则呢?这就是上面提到的问题。我们目前正在通过引入特定的观察者模式来添加被观察者的通知容器。如果警察四、old五…当警察越来越多的时候,原来的代码还是需要改的。如何解决问题?

其实很简单。通常,Web 项目基本上是使用框架开发的。自然需要利用特征来解决场景。问题。这里我们通过变换特定的观察对象来实现开闭原理

如果你读过前人写的设计模式文章,你不会对界面感到陌生。在方法中,我们通过注入的 IOC 容器获取所有的观察者对象,并将它们添加到观察者通知容器中。这种情况下触发了观察者事件,只需要一行代码就可以完成通知

@PostConstruct
public void executor() {

图片[2]-了解如何使用 Guava 和 Spring 设计观察者模式-唐朝资源网

// 被观察者触发事件, 通知所有观察者 subject.notify("阿祖有行动!"); }

如果以后添加新的观察者类,只需创建新类实现抽象观察者接口即可完成需求。有时候,不仅是工具的类型可以封装,一些设计模式也可以封装,以便更好的服务开发者灵活使用。在这里,我们将分别介绍 Guava# 和#event 模型。

同步和异步的概念

在介绍和事件模型之前,有一个不可逾越的转折,即同步执行、异步执行的概念,以及在什么场景下使用同步和异步模型?

这里举个例子,可以很好的体现同步和异步的概念。比如你要打电话给体检医院预约体检,你说要预约的时间后,对面的女士说:“等一下,我去查如果时间可以的话”,如果你这个时候不挂断,等那位女士检查并告诉你再挂断,那么这就是同步。如果她说她需要稍后检查,你告诉她:“我先挂断,等我查到结果后再回拨”,那么这就是异步+回调

在我们上面写的示例代码中,毫无疑问,观察者模式是以同步的形式执行的,那么是否可以以异步的方式执行观察者行为呢?答案当然是肯定的。我们可以通过在执行观察者模式行为之前创建一个线程来做到这一点,这自然是异步的。当然,不建议你这样做,可能会涉及更多问题。我们来看看 Guava 以及它是如何封装观察者模式的

番石榴解析

是Guava提供的消息发布-订阅类库,是设计模式中的观察者模式(生产/消费者模式的经典实现)

具体代码已上传至代码库。该实现包括两种方法:同步和异步。代码库通过同步方式实现观察者模式

因为和不是本文的重点,所以这里只讨论它的原理。第一个是同步类库。如果需要使用异步,则在创建时指定。

// 创建同步 EventBus
EventBus eventBus = new EventBus();
// 创建异步 AsyncEventBus
EventBus eventBus = new AsyncEventBus(Executors.newFixedThreadPool(10));

注意创建需要指定线程池,没有内部线程池。默认情况下指定。当然,不要像上面的代码那样直接使用创建,作者是为了省事,如果从规范来看,还是使用默认的线程池构造方式新建(xxx);

这里有一个更有趣的同步实现。观察者在操作同步和异步行为时,都是用来执行观察者的内部代码,那么如何保证可以同步执行呢? Guava 这样做:实现接口,重写执行方法,调用run方法

enum DirectExecutor implements Executor {
    INSTANCE;
    @Override
    public void execute(Runnable command) {
        command.run();
    }
}

有兴趣的可以去看源码,不难理解,在工作中使用还是蛮方便的。但也有一个缺点,因为它是一个进程内操作。如果使用异步执行业务,有丢失任务的可能。

事件模型

如果您想使用播放观察者模式,只需几个简单的步骤。总结:操作简单,功能强大

创建业务相关,需要继承,重写参数化构造函数

定义不同的监听器()如实现接口、重写

通过#方法发布特定事件

事件和Guava一样,代码不会被粘贴,已经存入代码仓库。这里我们重点介绍事件模型的特点和使用问题。

事件也支持异步编程,具体实现类需要添加@Async注解。支持订阅顺序,例如A、B、C三个。通过@Order注解可以实现多个观察者的顺序消费。

作者建议读者朋友一定要运行Demo。在使用框架的同时,也要合理使用框架提供的工具轮,因为它们是由框架封装的。出来的函数一般比自己写的函数更强大,出问题的可能性也更小。同时切记不要创建重复的轮子,除非功能点不满足,可以在原有轮子的基础上开发自己的功能。

结论

文章图文相助大家整理了观察者模式的实现,并推出了进阶版。相信看完之后,你可以在自己的项目中愉快的玩转设计模式。请记住在合理的场景中使用该模式。一般来说,观察者模式作用于观察者与被观察者之间的解耦。

最后,项目中提到的第一个问题得到了解答。观察者模式应该使用同步模型还是异步模型?

如果只是用观察者模式来拆分代码,使其符合开闭原则、高内聚、低耦合、单一职责,那么使用同步是很自然的。去做吧,因为这是最安全的方法。如果不关心观察者的执行结果或者考虑性能,可以使用异步的方式,通过回调满足业务返回需求

关于观察者设计模式的这篇文章就讲到这里,后续会陆续输出工厂、原型、享元等模式;如果文章对你有帮助,请关注并支持,祝你好运。

© 版权声明
THE END
喜欢就支持一下吧
点赞226 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片