关于后端:跟着-GuavaSpring-学习如何设计观察者模式

45次阅读

共计 5821 个字符,预计需要花费 15 分钟才能阅读完成。

文章首发在公众号(龙台的技术笔记),之后同步到掘金和集体网站:xiaomage.info

明天解说一篇行为型设计模式,什么是行为型?行为型次要负责设计 类或对象之间的交互。工作中罕用的观察者模式就是一种行为型设计模式

最近在尝试重构之前写过的代码。在从新梳理过业务之后,发现已有的设计场景应该可能接入到设计模式,而且查看了代码的提交记录,更是动摇了此想法

放弃之前的一贯作风,想要阐明一个设计模式,须要三板斧撑持。什么是观察者模式?如何应用观察者模式?我的项目中应该如何利用?

观察者设计模式纲要如下:

  1. 什么是观察者模式
  2. 观察者模式代码如何写
  3. 如何应用观察者模式联合业务
  4. Guava EventBus 观察者模式
  5. Spring ApplicationEvent 事件模型
  6. 观察者模式最初的总结

什么是观察者模式

观察者模式 是一种行为设计模式,容许定义一种订阅告诉机制,能够在对象(被观察者)事件产生时告诉多个“察看”该对象的观察者对象,所以也被称为 公布订阅模式

其实我集体而言,不太喜爱应用文字去定义一种设计模式的语义,因为这样总是难以了解。所以就有了上面生存中的例子,来帮忙读者更好的去了解模式的语义。类图如下所示:

在举例说明前,先让咱们相熟下观察者模式中的 角色类型 以及代码示例。观察者模式由以下几局部角色组成,能够参考代码示例去了解,不要被文字描述带偏

  • 主题(被观察者)(Subject):形象主题角色把所有观察者对象保留在一个容器里,提供增加和移除观察者接口,并且提供出告诉所有观察者对象接口(也有作者通过 Observable 形容)
  • 具体主题(具体被观察者)(Concrete Subject):具体主题角色的职责就是 实现形象指标角色的接口语义,在被观察者状态更改时,给容器内所有注册观察者发送状态告诉
public interface Subject {void register(Observer observer);  // 增加观察者
    void remove(Observer observer);  // 移除观察者
    void notify(String message);  // 告诉所有观察者事件
}

public class ConcreteSubject implements Subject {private static final List<Observer> 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)); }
}
  • 形象观察者(Observer):形象观察者角色是观察者的行为形象,它定义了一个批改接口,当被观察者收回事件时告诉本人
  • 具体观察者(Concrete Observer):实现形象观察者定义的更新接口,能够在被观察者收回事件时告诉本人
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
    public void update(String message) {
        // 执行 message 逻辑
        System.out.println("接管到被观察者状态变更 -2");
    }
}

咱们跑一下下面的观察者模式示例,如果不出意外的话会将两个观察者执行逻辑中的日志打印输出。如果是平时业务逻辑,形象观察者定义的入参是具备业务意义的,大家能够类比我的项目上应用到的 MQ Message 机制

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

观察者模式联合业务

因为公司业务场景窃密,所以上面咱们通过【新警察故事】的电影情节,略微篡改下剧情,模拟出咱们的观察者模式利用场景

假如:目前咱们有三个警察,别离是 龙哥、锋哥、老三 ,他们授命跟进立功嫌疑人 阿祖。如果发现立功嫌疑人阿祖有动静,龙哥、峰哥负责施行抓捕口头,老三向警察局摇人,流程图如下:

如果说应用惯例代码写这套流程,是可能实现需求的,一把梭的逻辑能够实现所有需要。然而,如果说下次口头,龙哥让老三跟着本人施行抓捕,亦或者说龙哥团队扩张,来了老四、老五、老六 …

比照观察者模式角色定义,老四、老五、老六都是具体的观察者(Concrete Observer)

如果依照下面的构想,咱们通过“一把梭”的形式把代码写进去会有什么问题呢?如下:

  1. 首当其冲,减少了代码的复杂性。实现类或者说这个办法函数奇大无比,因为随着警员的扩张,代码块会越来越大
  2. 违反了开闭准则,因为会频繁改变不同警员的工作。每个警员的工作不是变化无穷的,举个例子来说这次针对疑犯,让峰哥施行的抓捕口头,下次就可能是疏散民众,难道每次的更改都须要改变“一把梭”的代码

第一种咱们能够通过,大函数拆小函数 或者 大类拆分为小类 的形式解决代码负责性问题。然而,开闭准则却不能防止掉,因为随着警员(观察者)的增多及缩小,势必会面临频繁改变原函数的状况

当咱们面对这种 已知会变动 ,并且可能会 频繁变动不固定 的代码,就要应用抽象思维来进行设计,进而放弃代码的简洁、可保护

这里应用 Java SpringBoot 我的项目构造来书写观察者模式,代码最终推送到 Github 仓库。读者能够先把仓库拉下来,因为其中不止示例代码,还包含 Guava 和 Spring 的观察者模式实现,GitHub 仓库地址

首先,定义观察者模式中的观察者角色,别离为形象观察者接口以及三个具体观察者实现类。理论业务中,设计模式会和 Spring 框架相结合,所以示例代码中蕴含 Spring 相干注解及接口

其次,定义形象被观察者接口以及具体被观察者实现类。同上,被观察者也须要成为 Spring Bean,托管于 IOC 容器治理

到这里,一个残缺的观察者模式就实现了。然而,仔细的读者会发现这样的观察者模式会有一个小问题,这里先不阐明,持续往下看。接下来就须要理论操练一番,注册这些观察者,通过被观察者触发事件来告诉观察者

如何实现开闭准则

看了利用的代码之后,函数体过大的问题曾经被解决了,咱们通过 拆分成为不同的具体的观察者类 来拆分总体逻辑。然而开闭准则问题呢?这就是下面所说的问题所在,咱们目前是通过 显示的引入具体观察者模式 来进行增加到被观察者的告诉容器中,如果后续增加警察老四、老五 … 越来越多的警察时,还是须要改变原有代码,问题应该怎么解决呢

其实非常简单,平时 Web 我的项目根本都会应用 Spring 框架开发,那天然是要使用其中的个性解决场景问题。咱们这里通过 革新具体被观察者实现开闭准则

如果看过之前作者写过的设计模式文章,对 InitializingBean 接口不会感到生疏,咱们在 afterPropertiesSet 办法中,通过注入的 IOC 容器获取到所有观察者对象 并增加至被观察者告诉容器中。这样的话,触发观察者事件,代码中只须要一行即可实现告诉

@PostConstruct
public void executor() {
    // 被观察者触发事件, 告诉所有观察者
    subject.notify("阿祖有口头!");
}

后续如果再有新的观察者类增加,只须要创立新的类实现形象观察者接口即可实现需要。有时候,可能被封装起来的不止是 DateUtil 类型的工具类 ,一些设计模式也能够被封装, 继而更好的服务开发者灵活运用。这里会别离介绍 Guava#EventBus 以及 Spring# 事件模型

同步异步的概念

在介绍 EventBusSpring 事件模型之前,有一道绕不过来的弯,那就是同步执行、异步执行的概念,以及在什么样的场景下应用同步、异步模型?

  • 同步执行:所谓同步执行,指的就是在收回一个申请后,在没有取得调用后果之前,调用者就会期待在以后代码 。直到获取到调用办法的执行后果,才算是完结。总结一句话就是 由调用者被动期待这个调用的后果,未返回之前不执行别的操作
  • 异步执行:而异步执行恰恰相反,收回调用申请后立刻返回,并向下执行代码。异步调用办法个别不会有返回后果,调用之后就能够执行别的操作,个别通过回调函数的形式告诉调用者后果

这里给大家举个例子,可能很好的反馈同步、异步的概念。比如说你想要给体检医院打电话预约体检,你说出本人想要预约的工夫后,对面的小姐姐说:“稍等,我查一下工夫是否能够”,这个时候如果你 不挂电话,等着小姐姐查完通知你 之后才挂断电话,那这就是同步。如果她说稍等须要查一下, 你通知她:“我先挂了,查到后果后再打过去”,那这就是异步 + 回调

在咱们下面写的示例代码上,毋庸置疑是通过同步的模式执行观察者模式,那是否能够通过异步的形式执行观察者行为 ?答案当然是能够。咱们能够通过在 观察者模式行为执行前创立一个线程,那天然就是异步的。当然,不太倡议你这么做,这样可能会牵扯出更多的问题。一起来看下 Guava 和 Spring 是如何封装观察者模式

Guava EventBus 解析

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

具体代码已上传 GitHub 代码仓库,EventBus 实现中蕴含同步、异步两种形式,代码库中由同步形式实现观察者模式

因为 EventBus 并不是文章重点,所以这里只会对其原理进行探讨。首先 EventBus 是一个同步类库,如果须要应用异步的,那就创立时候指定 AsyncEventBus

// 创立同步 EventBus
EventBus eventBus = new EventBus();

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

<font color=’#FF0000′> 留神一点 </font>,创立 AsyncEventBus 须要指定线程池,其外部并没有默认指定。当然也别像下面代码间接用 Executors 创立,作者是为了图省事,如果从标准而言,还是消停的应用默认线程池构建办法创立 new ThreadPoolExecutor(xxx);

EventBus 同步实现有一个比拟有意思的点。观察者操作同步、异步行为时,均应用 Executor 去执行观察者外部代码,那如何保障 Executor 能同步执行呢。Guava 是这么做的:实现 Executor 接口,重写执行办法,调用 run 办法

enum DirectExecutor implements Executor {
    INSTANCE;

    @Override
    public void execute(Runnable command) {command.run();
    }
}

大家有趣味能够去看下 EventBus 源码,不是很难了解,工作应用上还是挺不便的。只不过也有不好的中央,因为 EventBus 属于过程内操作,如果应用异步 AsyncEventBus 执行业务,存在失落工作的可能

Spring 事件模型

如果想要应用 ApplicationEvent 玩转观察者模式,只须要简略几步。总结:操作简略,功能强大

  1. 创立业务相干的 MyEvent,须要继承 ApplicationEvent,重写有参构造函数
  2. 定义不同的监听器(观察者)比方 ListenerOne 实现 ApplicationListener<MyEvent> 接口,重写 onApplicationEvent 办法
  3. 通过 ApplicationContext#publishEvent 办法公布具体事件

Spring 事件与 Guava EventBus 一样,代码就不粘贴了,都曾经寄存到 Github 代码仓库。这里重点介绍下 Spring 事件模型的特点,以及应用事项

Spring 事件同样反对异步编程,须要在具体 Listener 实现类上增加 @Async 注解。反对 Listener 订阅的程序,比如说有 A、B、C 三个 Listener。能够通过 @Order 注解实现多个观察者程序生产

作者倡议读者敌人肯定要跑下 ApplicationEvent 的 Demo,在应用框架的同时也 要正当的使用框架提供的工具轮子 ,因为被框架封装出的性能,一般而言要比本人写的性能更弱小、呈现问题的几率更少。同时, 切记不要造反复轮子,除非性能点不满足的状况下,能够借鉴原有轮子的根底上开发本人性能

结言

文章通过图文并茂的形式帮忙大家梳理了下观察者模式的实现形式,更是推出了进阶版的 EventBus 以及 ApplicationEvent,置信大家看完之后能够很欢快的在本人我的项目中游玩设计模式了。切记哈,要在正当的场景下应用模式,一般而言观察者模式作用于 观察者与被观察者之间的解耦合

最初解答下最早提到的问题,我的项目中的观察者模式 应该应用同步模型还是异步模型呢

如果只是应用观察者模式拆分代码使其满足 开闭准则、高内聚低耦合、职责繁多 等个性,那么天然是应用同步去做,因为这种形式是最为稳当。而如果 不关怀观察者执行后果或者思考性能 等状况,则能够应用异步的形式,通过回调的形式满足业务返回需要

对于观察者设计模式本文就讲到这里,前面会陆续输入工厂、原型、享元等模式;如果文章对你有帮忙那就点个关注反对下吧,祝好。

正文完
 0