设计模式之观察者模式

文章目录

一、介绍

观察者模式(Observer Pattern) ,又称监听器模式(Listener Pattern)发布-订阅模式(Publish-Subscribe Pattern) ,属于行为型设计模式 。该模式定义了一种一对多的依赖关系,当一个对象状态发生改变或执行某个逻辑时,所有依赖于该动作的对象都会收到通知并作出相应的动作。

该模式的目的就是为了使系统中的主线业务 与各个与主线业务相关的支线业务 之间实现解耦

比如我们社会中,各行各业的所有企业发展都离不开国家政策的支持,当国家发布某一个政策时,社会上各行各业所有企事业单位都会根据该政策对内部作出相应的调整。在这个例子中,国家发布政策相当于主线业务,依赖于该政策的各个企事业单位做出的相应调整就相当于支线业务了。

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

二、实现思路

在观察者模式中,存在两个重要角色:观察者被观察者

被观察者中维护一组观察者的集合,当调用被观察者的某个行为时,其内部对观察者集合进行遍历并调用各个观察者的对应逻辑。

三、基本角色

通过以上介绍和实现思路,我们围绕观察者模式中观察者和被观察者两个部分定义出以下四个基本角色:

  • 被观察者的抽象接口(Observable)

    该接口定义被观察者的基本功能,如添加观察者addObserver()、向观察者发送提醒notifyObs()、获取被观察者中的内容getContent()等方法。

    java 复制代码
    public interface Observable {
    
        /** 添加观察者 */
        void addObserver(Observer observer);
    
        /** 向观察者发送提醒 */
        void notifyObs();
    
        /** 获取内容 */
        String getContent();
    }
  • 被观察者的抽象类(AbsObservable)

    实现抽象接口Observable,对接口中定义的方法实现基本逻辑,并在其内部维护一个观察者集合。该抽象类是观察者模式中的关键角色,其内部定义了观察者模式中的关键代码逻辑。

    java 复制代码
    public abstract class AbsObservable implements Observable{
    
        // 观察者集合
        private final List<Observer> observerList = new ArrayList<>();
    
        // 被观察者的内容
        private final String content;
    
        // 强制其子类重写构造函数
        public AbsObservable(String content) {
            this.content = content;
        }
    
        @Override
        public void addObserver(Observer observer) {
            observerList.add(observer);
        }
    
        @Override
        public void notifyObs() {
            // 通知集合中的所有观察者,对当前被观察者作出相应更新
            for (Observer observer : observerList) {
                observer.update(this);
            }
        }
    
        // 获取当前被观察者的内容
        @Override
        public String getContent() {
            return content;
        }
    
    }
  • 被观察者的具体实现类(ObservableImpl)

    继承于抽象类AbsObservable,并显示定义其构造方法以定义该被观察者的内容content

    由于被观察者的基本实现逻辑都已经在抽象类AbsObservable中实现,因此无需对其重写。

    java 复制代码
    public class ObservableA extends AbsObservable {
    
        public ObservableA(String content) {
            super(content);
        }
    }
    
    public class ObservableB extends AbsObservable {
    
        public ObservableB(String content) {
            super(content);
        }
    }
  • 观察者的抽象接口(Observer)

    定义观察者的基本功能,如根据其观察的被观察者做出响应update()

    java 复制代码
    public interface Observer {
    	// 对被观察者对象做出响应
        void update(AbsObservable observable);
    }
  • 观察者的具体实现类(ObserverImpl)

    实现抽象接口Observer,对其定义的方法进行实现。

    java 复制代码
    public class ObserverA implements Observer{
    
        @Override
        public void update(AbsObservable observable) {
            System.out.println("ObserverA接收到内容:" + observable.getContent());
        }
    }
    
    public class ObserverB implements Observer{
    
        @Override
        public void update(AbsObservable observable) {
            System.out.println("ObserverB接收到内容:" + observable.getContent());
        }
    }
  • 新建测试类(ObserverDemo)

    java 复制代码
    public class ObserverDemo {
    
        public static void main(String[] args) {
            // 定义两个观察者:observerA 和 observerB
            Observer observerA = new ObserverA();
            Observer observerB = new ObserverB();
    
            // 定义被观察者observableA,向其添加观察者,
            Observable observableA = new ObservableA("来自ObservableA的消息");
            observableA.addObserver(observerA);
            observableA.addObserver(observerB);
            observableA.notifyObs();
    
            System.out.println("=====================");
    
    
            // 定义被观察者observableB,向其添加观察者,
            Observable observableB = new ObservableB("来自ObservableB的消息");
            observableB.addObserver(observerA);
            observableB.addObserver(observerB);
            observableB.notifyObs();
        }
    }

    运行该演示代码,输出如下

观察者模式的通用UML类图如下所示

四、案例

我们以政府发布政策,各企事业单位响应政策为例。

我们政府需要根据社会实际发展状况针对各行各业发布各种政策,有教育行业政策、互联网行业政策等,我们熟知的公司比如教育行业的新东方、互联网行业的阿里和百度会根据各个政策作出响应,处于教育行业的新东方对教育行业政策十分关注,而处于互联网行业的阿里百度则对互联网行业政策十分关注,无论什么公司,当自己关注的新政策发布时,都会做出响应。

我们把这个案例结合观察者模式进行分析,政府针对各行各业发布的政策就相当于被观察者,新东方阿里百度这三家公司就相当于观察者。

下面我们通过不使用观察者模式和使用观察者模式两种实现方案的对比,对观察者模式有个更加深入的了解。

1. 不使用观察者模式

在不使用观察者模式的情况下,要实现上面案例,代码如下所示

  • 公司抽象接口类Company

    每个具体的公司都应当具备**响应政策(responsePolicy)**的功能

    java 复制代码
    public interface Company {
    
        /** 响应政策 */
        void responsePolicy();
    }
  • 公司实现类AliBaiduXinDongFang

    java 复制代码
    public class Ali implements Company{
        @Override
        public void responsePolicy() {
            System.out.println("阿里巴巴响应新政策");
        }
    }
    
    public class Baidu implements Company{
        @Override
        public void responsePolicy() {
            System.out.println("百度响应新政策");
        }
    }
    
    public class XinDongFang implements Company{
        @Override
        public void responsePolicy() {
            System.out.println("新东方响应新政策");
        }
    }
  • 政策抽象接口类Policy

    java 复制代码
    public interface Policy {
    
        /** 发布新政策 */
        void publish();
    }
  • 政策实现类EduPolicyInternetPolicy

    在调用政策的发布方法publish()后,需要调用每一个公司的响应政策方法,比如在教育政策发布后,需要调用教育行业公司的响应政策方法,这种方式需要在政策类内部维护大量相关的公司,以便于在发布政策时对这些公司逐个调用其响应政策方法,而根据单一职责原则,这种方式明显使方法变得臃肿。

    java 复制代码
    public class EduPolicy implements Policy{
    
        private final String content;
    
        public EduPolicy(String content) {
            this.content = content;
        }
    
        @Override
        public void publish() {
            System.out.println("教育政策发布:" + content);
    
            // 公司响应政策
            Company xinDongFang = new XinDongFang();
            xinDongFang.responsePolicy();
        }
    }
    
    public class InternetPolicy implements Policy{
    
        private final String content;
    
        public InternetPolicy(String content) {
            this.content = content;
        }
    
        @Override
        public void publish() {
            System.out.println("互联网政策发布:" + content);
    
            // 公司响应政策
            Company ali = new Ali();
            ali.responsePolicy();
            Company baidu = new Baidu();
            baidu.responsePolicy();
        }
    }
  • 代码演示

    java 复制代码
    public class NoObserverDemo {
    
        public static void main(String[] args) {
            Policy eduPolicy = new EduPolicy("教育行业新政策");
            eduPolicy.publish();
    
            System.out.println("======================");
    
            Policy internetPolicy = new InternetPolicy("互联网行业新政策");
            internetPolicy.publish();
        }
    }
  • 结果输出

在不使用观察者模式时,会出现主线业务代码中混入大量分支业务代码,形成不相关逻辑之间的强耦合,使得代码变得十分臃肿,违背单一职责原则。

2. 使用观察者模式

在使用观察者模式后,主线业务代码与分值业务代码解耦,代码变得简洁明了。

下面我们对上面代码进行改进

  • 公司抽象接口类Company

    与上面不同的是,响应政策方法添加了一个政策参数。这就要求各行各业所有公司无差别的对任何政策进行响应,可以积极响应,当然也可以消极响应(啥也不干)。

    java 复制代码
    public interface Company {
    
        /** 响应政策 */
        void responsePolicy(Policy policy);
    }
  • 公司实现类AliBaiduXinDongFang

    在下面的改进代码中,每一个公司都可以接收到新政策,但具体怎么做,可以根据接收到的新政策是否是该公司关注的。比如互联网公司只关注互联网新政策,教育公司只关注教育行业新政策。

    java 复制代码
    public class Ali implements Company{
        @Override
        public void responsePolicy(Policy policy) {
            if (policy instanceof InternetPolicy) {
                System.out.println("阿里巴巴响应新政策");
            }
        }
    }
    
    public class Baidu implements Company{
        @Override
        public void responsePolicy(Policy policy) {
            if (policy instanceof InternetPolicy) {
                System.out.println("百度响应新政策");
            }
        }
    }
    
    public class XinDongFang implements Company{
        @Override
        public void responsePolicy(Policy policy) {
            if (policy instanceof EduPolicy) {
                System.out.println("新东方响应新政策");
            }
        }
    }
  • 政策抽象接口类Policy

    改进后的Policy接口,除了发布政策publish()功能,还应具备一些被观察者的其他特征,如添加关注政策的公司addCompany(),获取政策内容getContent()等。

    java 复制代码
    public interface Policy {
    
        void addCompany(Company company);
    
        /** 政策发布 */
        void publish();
    
        String getContent();
    }
  • 政策抽象类AbsPolicy

    抽象类是观察者模式中十分重要的一个角色。该类定义了观察者模式中的基本代码逻辑,例如在publish()方法中,定义了政策发布的基本逻辑,即通知所有公司响应政策,这样一来,在其具体子类发布政策时,只需要关注其主线逻辑即可,在最后直接通过super.publish()将政策传播给各个公司即可。

    java 复制代码
    public abstract class AbsPolicy implements Policy{
    
        // 观察者集合
        private final List<Company> companyList = new ArrayList<>();
    
        // 被观察者的内容
        protected final String content;
    
        // 强制其子类重写构造函数
        public AbsPolicy(String content) {
            this.content = content;
    
            companyList.add(new Ali());
            companyList.add(new Baidu());
            companyList.add(new XinDongFang());
        }
    
        @Override
        public void addCompany(Company company) {
            companyList.add(company);
        }
    
        @Override
        public void publish() {
            // 通知集合中的所有观察者,对当前被观察者作出相应更新
            for (Company company : companyList) {
                company.responsePolicy(this);
            }
        }
    
        // 获取当前被观察者的内容
        @Override
        public String getContent() {
            return content;
        }
    }
  • 政策实现类EduPolicyInternetPolicy

    在应用观察者模式后,发布政策的主线逻辑与公司对政策的响应逻辑实现解耦,只需要通过super.publish()将政策传播给各个公司即可。

    java 复制代码
    public class EduPolicy extends AbsPolicy {
    
        public EduPolicy(String content) {
            super(content);
        }
    
        @Override
        public void publish() {
            System.out.println("教育政策发布:" + content);
    
            super.publish();
        }
    }
    
    public class InternetPolicy extends AbsPolicy {
    
        public InternetPolicy(String content) {
            super(content);
        }
    
        @Override
        public void publish() {
            System.out.println("互联网政策发布:" + content);
    
            super.publish();
        }
    }
  • 代码演示

    java 复制代码
    public class PolicyDemo {
    
        public static void main(String[] args) {
    
            Policy eduPolicy = new EduPolicy("教育行业新政策");
            eduPolicy.publish();
    
            System.out.println("==============");
            Policy internetPolicyolicy = new InternetPolicy("互联网行业新政策");
            internetPolicyolicy.publish();
        }
    }

    输出如下

五、java中的观察者模式

在java中,java.util包中同样为我们提供了观察者模式的框架供我们使用。有兴趣的朋友可以亲自查看源码,超级简单,该框架只提供一个具体类Observable和一个抽象接口类Observer

  • java.util.Observable
  • java.util.Observer

这两个类的用法与我们上面的讲解代码相似度高达80%,最大的不同就是在JDK的实现中还增加了一个布尔类型的changed域,通过设置这个变量来确定是否通知观察者。也就是说java提供的观察者模式框架我们只需要学习了解即可,在实际生产工作中,不建议使用。

六、spring中的观察者模式

如今我们开发使用的几乎都是spring全家桶了,那么spring是否给我们提供了观察者模式的相关框架呢?答案是肯定的。

在spring中,通过上下文发布指定事件ApplicationEvent ,监听该事件的**监听器ApplicationListener**处理对应逻辑。这种实现更贴合 发布-订阅模式 这个称呼,其中事件ApplicationEvent相当于观察者模式中的Observable,而监听器ApplicationListener相当于观察者模式中的Observer

下面我们学习一下如何使用

  • 定义一个事件

    java 复制代码
    public class CarEvent extends ApplicationEvent {
        /**
         * Create a new {@code ApplicationEvent}.
         *
         * @param source the object on which the event initially occurred or with
         *               which the event is associated (never {@code null})
         */
        public CarEvent(Object source) {
            super(source);
        }
    }
  • 定义一个针对于该事件的监听器

    • 方式一

      通过实现ApplicationListener接口定义监听器

      java 复制代码
      @Component
      public class CarEventListener implements ApplicationListener<CarEvent> {
      
          @Override
          public void onApplicationEvent(CarEvent event) {
              // 监听器监听到指定事件
              // 针对该事件处理逻辑...
          }
      }
    • 方式二

      通过注解@EventListener定义监听器

      java 复制代码
      @Slf4j
      @Component
      public class GlobalListener {
      
          @EventListener
          public void carEvent(CarEvent carEvent) {
              log.info("监听到了car事件");
          }
      
      }
  • 发布事件

    应用上下文ApplicationContext具有发布事件的能力

    java 复制代码
    @Service
    public class OrderService {
    
        @Autowired
        private ApplicationContext context;
    
        public void doSomething() {
            // 使用上下文发布一个CarEvent
            context.publishEvent(new CarEvent(this));
        }
    }

七、优缺点

优点:

  • 观察者和被观察者是松耦合的,符合单一职责原则。
  • 定义了一对多的依赖关系。

缺点:

  • 如果观察者数量过多,则事件通知会耗时较长。可通过异步通知解决。
  • 如果观察者和被观察者之间存在循环依赖,则有可能导致系统崩溃。

纸上得来终觉浅,绝知此事要躬行。

------------------------我是万万岁,我们下期再见------------------------

相关推荐
_oP_i1 小时前
Pinpoint 是一个开源的分布式追踪系统
java·分布式·开源
mmsx1 小时前
android sqlite 数据库简单封装示例(java)
android·java·数据库
武子康1 小时前
大数据-258 离线数仓 - Griffin架构 配置安装 Livy 架构设计 解压配置 Hadoop Hive
java·大数据·数据仓库·hive·hadoop·架构
豪宇刘2 小时前
MyBatis的面试题以及详细解答二
java·servlet·tomcat
秋恬意2 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
FF在路上3 小时前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
真的很上进3 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
众拾达人4 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
皓木.4 小时前
Mybatis-Plus
java·开发语言
不良人天码星4 小时前
lombok插件不生效
java·开发语言·intellij-idea