观察者模式可以说是众多设计模式中, 最容易理解的设计模式之一了, 观察者模式在 Spring 中也随处可见, 面试的时候, 面试官可能会问, 嘿, 你既然读过 Spring 源码, 那你说说 Spring 中运用的设计模式吧, 你可以自信的告诉他, Spring 中的 ApplicationListener 就运用了观察者模式.
让我们一步一步来, 首先我们要知道到底什么是观察者模式, 用 Java 是如何实现的, 在这里, 我将会用三种方式来实现观察者模式.
什么是观察者模式
在现实生活中, 观察者模式处处可见, 比如
看新闻, 只要新闻开始播放了, 就会把新闻推送给订阅了新闻的用户, 在这里, 新闻就是[被观察者] , 而用户就是[观察者] .
微信公众号, 如果一个用户订阅了某个公众号, 那么便会收到公众号发来的消息, 那么, 公众号就是[被观察者] , 而用户就是[观察者] .
热水器, 假设热水器由三部分组成, 热水器, 警报器, 显示器, 热水器仅仅负责烧水, 当水温到达设定的温度后, 通知警报器, 警报器发出警报, 显示器也需要订阅热水器的烧水事件, 从而获得水温, 并显示. 热水器就是[被观察者] , 警报器, 显示器就是[观察者] .
在这里, 可以看到,[观察者] 已经失去自主的权利, 只能被动的接收来自[被观察者] 的事件, 无法主动观察.[观察者] 成为了 "受", 而[被观察者] 成为了 "攻".[被观察者] 只是通知[观察者] , 不关心[观察者] 收到通知后, 会执行怎样的动作.
而在设计模式中, 又把[被观察者] 称为[主题] .
在观察者设计模式中, 一般有四个角色:
抽象主题角色(Subject)
具体主题角色(ConcreteSubject)
抽象观察者角色(Observer)
具体观察者角色(ConcreteObserver)
其中,[主题] 需要有一个列表字段, 用来保存 [观察者] 的引用, 提供两个方法(虚方法), 即[删除观察者] [增加观察者] , 还需要提供一个给客户端调用的方法, 通知各个[观察者] : 你们关心(订阅) 的事件已经推送给你们了.
下面, 我就用三种方式来实现观察者模式.
经典
- public class News {
- private String title;
- private String content;
- public String getTitle() {
- return title;
- }
- public void setTitle(String title) {
- this.title = title;
- }
- public String getContent() {
- return content;
- }
- public void setContent(String content) {
- this.content = content;
- }
- }
此类不属于观察者模式必须的类, 用来存放事件的信息.
- public interface Subject {
- List<People> peopleList = new ArrayList<>();
- default void add(People people) {
- peopleList.add(people);
- }
- default void remove(People people) {
- peopleList.remove(people);
- }
- void update();
- }
抽象主题角色, 在这个角色中, 有一个字段 peopleList, 用来保存[观察者] 的引用, 同时定义了两个接口, 这是 Java8 默认接口实现的写法. 这两个接口是给客户端调用的, 用来[删除观察者] [增加观察者] , 还提供一个方法, 此方法需要被[具体主题角色] 重写, 用来通知各个[观察者] .
- public class NewsSubject implements Subject{
- public void update() {
- for (People people : peopleList) {
- News news = new News();
- news.setContent("今日在大街上, 有人躲在草丛中袭击路人, 还大喊" 德玛西亚万岁 "");
- news.setTitle("德玛西亚出现了");
- people.update(news);
- }
- }
- }
具体主题角色, 重写了[抽象主题角色] 的方法, 循环列表, 通知各个[观察者] .
- public interface People {
- void update(News news);
- }
抽象观察者角色, 定义了一个接口,[具体观察者角色] 需要重写这个方法.
下面就是[具体观察者角色] 了:
- public class PeopleA implements People {
- @Override
- public void update(News news) {
- System.out.println("这个新闻真好看");
- }
- }
- public class PeopleB implements People {
- @Override
- public void update(News news) {
- System.out.println("这个新闻真无语");
- }
- }
- public class PeopleC implements People {
- @Override
- public void update(News news) {
- System.out.println("这个新闻真逗");
- }
- }
客户端:
- public class Main {
- public static void main(String[] args) {
- Subject subject = new NewsSubject();
- subject.add(new PeopleA());
- subject.add(new PeopleB());
- subject.add(new PeopleC());
- subject.update();
- }
- }
运行:
image.PNG
我们学习设计模式, 必须知道设计模式的优缺点, 那么观察者设计模式的优缺点是什么呢?
优点:
[主题] 和[观察者] 通过抽象, 建立了一个松耦合的关系,[主题] 只知道当前有哪些[观察者] , 并且发送通知, 但是不知道[观察者] 具体会执行怎样的动作. 这也很好理解, 比如 微信公众号推送了一个消息过来, 它不知道你会采取如何的动作, 是 微笑的打开, 还是愤怒的打开, 或者是直接把消息删了, 又或者把手机扔到洗衣机洗刷刷.
符合开闭原则, 如果需要新增一个[观察者] , 只需要写一个类去实现[抽象观察者角色] 即可, 不需要改动原来的代码.
缺点:
客户端必须知道所有的[观察者] , 并且进行[增加观察者] 和[删除观察者] 的操作.
如果有很多[观察者] , 那么所有的[观察者] 收到通知, 可能需要花费很久时间.
当然以上优缺点, 是最直观的, 可以很容易理解, 并且体会到的. 其他优缺点, 可以自行百度.
Lambda
在介绍这种写法之前, 有必要介绍下函数式接口, 函数式接口的概念由来已久, 一般来说只定义了一个虚方法的接口就叫函数式接口, 在 Java8 中, 由于 Lambda 表达式的出现, 让函数式接口大放异彩.
我们仅仅需要修改客户端的代码就可以:
- public static void main(String[] args) {
- Subject subject = new NewsSubject();
- subject.add(a -> System.out.println("已阅这新闻"));
- subject.add(a -> System.out.println("假的吧"));
- subject.add(a -> System.out.println("昨天就看过了"));
- subject.update();
- }
运行结果:
image.PNG
利用 Lambda 表达式和函数式接口, 可以省去[具体观察者角色] 的定义, 但是个人认为, 这并非属于严格意义上的观察者模式, 而且弊端很明显:
客户端需要知道观察者的具体实现.
如果观察者的具体实现比较复杂, 可能代码并没有那么清晰.
所以这种写法, 具有一定的局限性.
借用大神的一句话
设计模式的出现, 是为了弥补语言的缺陷.
正是由于语言的升级, 让某些设计模式发生了一定的变化, 除了观察者模式, 还有模板方法模式, 责任链模式等, 都由于 Lambda 表达式的出现, 而出现了一些变化.
JDK
在 Java 中, 本身就提供了一个接口: Observer, 一个子类: Observable, 其中 Observer 表示[观察者] ,Observable 表示[主题] , 可以利用这两个子类和接口来实现观察者模式:
- public class NewsObservable extends Observable {
- public void update() {
- setChanged();
- notifyObservers();
- }
- }
- public class People1 implements Observer {
- @Override
- public void update(Observable o, Object arg) {
- System.out.println("小编真无聊");
- }
- }
- public class People2 implements Observer {
- @Override
- public void update(Observable o, Object arg) {
- System.out.println("开局一张图, 内容全靠编");
- }
- }
客户端:
- public static void main(String[] args) {
- NewsObservable newsObservable = new NewsObservable();
- newsObservable.addObserver(new People1());
- newsObservable.addObserver(new People2());
- newsObservable.update();
- }
运行结果:
image.PNG
在这里, 我不打算详细介绍这种实现方式, 因为从 Java9 开始, Java 已经不推荐这种写法了, 而推荐用消息队列来实现. 是不是很开心, 找到一个借口不去研究 Observable,Observer 这两个东西了.
Spring 中的事件编程模型
Spring 中的事件编程模型就是观察者模式的实现, SpringBoot 就利用了 Spring 的事件编程模型来完成一些操作, 这里暂时不表.
在 Spring 中定义了一个 ApplicationListener 接口, 从名字就知道它是一个监听器, 是监听 Application 的事件的, 那么 Application 又是什么, 就是 ApplicationContext,ApplicationContext 内置了几个事件, 其中比较容易理解的是:
- ContextRefreshedEvent
- ContextStartedEvent
- ContextStoppedEvent
- ContextClosedEvent
从名称上来看, 就知道这几个事件是什么时候被触发的了.
下面我演示下具体的用法, 比如我想监听 ContextRefreshedEvent 事件, 如果事件发生了, 就打印一句话.
- @Component
- public class MyListener implements ApplicationListener{
- @Override
- public void onApplicationEvent(ApplicationEvent applicationEvent) {
- if(applicationEvent instanceof ContextRefreshedEvent){
- System.out.println("刷新了");
- }
- }
- }
- @Configuration
- @ComponentScan
- public class AppConfig {
- }
- public static void main(String[] args) {
- AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);
- }
运行结果:
image.PNG
当时学习 Spring, 看到 Spring 提供了各式各样的接口来让程序员们对 Spring 进行扩展, 并且没有任何侵入性, 我不得不佩服 Spring 的开发者们. 这里也是, 我们可以看到在客户端找不到任何关于 "订阅事件" 的影子.
这种实现方式不是太好, 可以看到我们在方法内部做了一个判断: 接收到的事件是否为 ContextRefreshedEvent.
伟大的 Spring 还提供了泛型的 ApplicationListener, 我们可以通过泛型的 ApplicationListener 来完善上面的代码:
- @Component
- public class MyListener implements ApplicationListener<ContextRefreshedEvent> {
- @Override
- public void onApplicationEvent(ContextRefreshedEvent event) {
- System.out.println("刷新了");
- }
- }
我们还可以利用 Spring 中的事件编程模型来自定义事件, 并且发布事件:
首先, 我们需要定义一个事件, 来实现 ApplicationEvent 接口, 代表这是一个 Application 事件, 其实上面所说的四个内置的事件也实现了 ApplicationEvent 接口:
- public class MyEvent extends ApplicationEvent {
- public MyEvent(Object source) {
- super(source);
- }
- }
还需要定义一个监听器, 当然, 在这里需要监听 MyEvent 事件:
- @Component
- public class MyListener implements ApplicationListener<MyEvent> {
- @Override
- public void onApplicationEvent(MyEvent event) {
- System.out.println("我订阅的事件已经到达");
- }
- }
现在有了事件, 也有了监听器, 是不是还少了发布者, 不然谁去发布事件呢?
- @Component
- public class MyEventPublish implements ApplicationEventPublisherAware {
- private ApplicationEventPublisher publisher;
- @Override
- public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
- this.publisher = applicationEventPublisher;
- }
- public void publish(Object obj) {
- this.publisher.publishEvent(obj);
- }
- }
发布者, 需要实现 ApplicationEventPublisherAware 接口, 重写 publish 方法, 顾名思义, 这就是发布方法, 那么方法的参数 obj 是干嘛的呢, 作为发布者, 应该需要知道我要发布什么事件, 以及事件来源 (是谁触发的) 把, 这个 obj 就是用来存放这样的数据的, 当然, 这个参数需要我们手动传入进去. setApplicationEventPublisher 是 Spring 内部主动调用的, 可以简单的理解为初始化发布者.
现在就剩最后一个角色了, 监听器有了, 发布者有了, 事件也有了, 对, 没错, 还少一个触发者, 毕竟要有触发者去触发事件啊:
- @Component
- public class Service {
- @Autowired
- private MyEventPublish publish;
- public void publish() {
- publish.publish(new MyEvent(this));
- }
- }
其中 publish 方法就是给客户端调用的, 用来触发事件, 可以很清楚的看到传入了 new MyEvent(this), 这样发布者就可以知道我要触发什么事件和是谁触发了事件.
当然, 还需要把一切交给 Spring 管理:
- @Configuration
- @ComponentScan
- public class AppConfig {
- }
客户端:
- public static void main(String[] args) {
- AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);
- context.getBean(Service.class).publish();;
- }
运行结果:
image.PNG
这一篇博客比较简单, 只是简单的应用, 但是只有会了应用, 才能谈源码.
这篇博客到这里就结束了, 谢谢大家.
来源: http://www.jianshu.com/p/5e72c6b76c72