设计模式之观察者模式

351 阅读8分钟

前面介绍了 工厂设计模式,这篇文章来 记录一种行为模式-- 观察者模式

在日常开发中,类、对象大多数情况下不是孤立存在,实现某些功能是需要类之间相互关联。总是存在某种关联关系。这种关系不仅存在类和对象之间,也存在模块和模块之间。所以在设计系统的时候是朝着解耦的方向去的,尽可能的降低模块之间的关联关系。现在记录的观察者模式就是为了降低对象之间耦合的一种行为模式。

需求:设计一个公司摸鱼报警系统

在一个阳光明媚的上午,在认真码代码的我发现,老板提着包走出了公司。于是大家不约而同的打开了股票,聊起了微信,看起了今日时事。正当大家以为美好的一天就要在摸鱼中度过时,前台小姐姐突然来到了办公区,让某某去一趟老板的办公室,紧接着然后就在摸鱼群里收到了前台小姐姐的消息,原来上午的时候某某被发现在看股票让我们以后要收敛一点。这时候有同事便说道下次老板回来的时候,在群里提醒我们一下。好了故事先讲到这里,在上面场景中可以便可以体现观察者模式。

什么是观察者模式

定义

观察者模式定义一种一对多的关系,让多个观察者对象同时订阅某一个主题对象,当这个主题对象的属性或者行为发生变化事时,会通知所有的观察者对象,让它们自己更新自己。

观察者的结构

抽象观察者: 是具体观察者的抽象类,主要的定义一个更新接口,使得可以得到被观察者更改通知时更新自己。
抽象被观察者(抽象主题): 是被观察者的抽象,主要定义个一个集合用于存储观察了该类的观察者对象,被观察者角色提供一个接口,可以增加、删除观察者对象。
具体的观察者: 是观察者的具体实现,并实现抽象观察者更新的接口,以便在得到被观察者更改通知时更新自己。
具体的被观察者(具体主题): 是被观察者的具体实现,同时将有关状态存入具体观察者对象,在具体的被观察者的内部状态发生改变时,给所有登记过的观察者发出通知

类图(UML)

image.png

代码

  1. 最普通的实现方式
  • 观察者
package com.chou.normal.observer;

import com.chou.normal.subject.Secretary;

/**
* @Author Chou
* @Description 看股票的同事
* @ClassName StockOberver
* @Date 2023/5/2 22:06
* @Version 1.0
**/
public class StockObserver {

   private String name;
   private Secretary secretary;

   public StockObserver(){}

   public StockObserver(String name,Secretary secretary){
       this.name = name;
       this.secretary = secretary;
   }

   public void update(){
       System.out.println(String.format("%s,%s 关闭股票行情网页,继续搬砖.....", secretary.getAction(), name));
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public Secretary getSecretary() {
       return secretary;
   }

   public void setSecretary(Secretary secretary) {
       this.secretary = secretary;
   }
}
  • 被观察者(主题)
package com.chou.normal.subject;

import com.chou.normal.observer.StockObserver;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author Chou
 * @Description 主题,对应着前台
 * @ClassName Secretary
 * @Date 2023/5/2 22:15
 * @Version 1.0
 **/
public class Secretary {

    /**
     * 查看股票同事的集合
     */
    private static final List<StockObserver> observers = new ArrayList<>();

    // 前台小姐姐做出的动作,可以看成是一个状态的改变
    private String action;

    public Secretary(){}

    public Secretary(String action) {
        this.action = action;
    }

    public void notice(){
        for (StockObserver observer : observers) {
            observer.update();
        }
    }

    public void attach(StockObserver observer){
        observers.add(observer);
    }

    public String getAction() {
        return action;
    }

    public void setAction(String action) {
        this.action = action;
    }
}
  • client
package com.chou;

import com.chou.normal.observer.StockObserver;
import com.chou.normal.subject.Secretary;

public class Client {
    public static void main(String[] args) {
        System.out.println("Hello world!");
        normalAction();
    }

    /**
     * 普通的方式实现摸鱼通知程序,主要缺点
     * 1、Observer类 和 subject类 高度耦合。如果需要在做其他事情的同事,则需要更改 secretary 中的变量,并且还需要需求notice() 方法,来通知。
     * 其中 subject类状态也是存在多种的,所以在Observer 类中update() 方法也需要修改,违背了 开闭原则 (对软件程序的修改关闭,对程序的扩展开发)
     * 2、在编程中应该是面对抽象或者说是接口编程,在update(),和 attach() 的入参进行修改。
     */
    private static void normalAction() {
        Secretary xiaoJiang = new Secretary();
        StockObserver tongShi1 = new StockObserver("张三", xiaoJiang);
        StockObserver tongShi2 = new StockObserver("李四", xiaoJiang);
        xiaoJiang.attach(tongShi1);
        xiaoJiang.attach(tongShi2);
        xiaoJiang.setAction("老板回来啦!!!!");
        xiaoJiang.notice();
    }
}

普通的方式实现摸鱼通知程序,有很明显的缺陷\

1、Observer类 和 subject类 高度耦合。如果需要在做其他事情的同事,则需要更改 secretary 中的变量,并且还需要需求notice() 方法来通知。 其中 subject类状态也是存在多种的,所以在Observer 类中update() 方法也需要修改,违背了 开闭原则 (对软件程序的修改关闭,对程序的扩展开发)
2、在编程中应该是面对抽象或者说是接口编程,在update(),和 attach() 的入参进行修改。

  1. 观察者模式实现(面向抽象编程)
  • 抽象被观察者
package com.chou.pattern.observer;

import com.chou.pattern.subject.Subject;

/**
 * @Author Chou
 * @Description 观察者的抽象
 * @ClassName Observer
 * @Date 2023/5/3 11:50
 * @Version 1.0
 **/
public abstract class Observer {

   private String name;

   private Subject subject;

   Observer(){};

   Observer(String name,Subject subject){
      this.name=name;
      this.subject = subject;
   }
   public abstract void update();

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }

   public Subject getSubject() {
      return subject;
   }

   public void setSubject(Subject subject) {
      this.subject = subject;
   }
}
  • 抽象观察者
package com.chou.pattern.observer;

import com.chou.pattern.subject.Subject;

/**
 * @Author Chou
 * @Description 观察者的抽象
 * @ClassName Observer
 * @Date 2023/5/3 11:50
 * @Version 1.0
 **/
public abstract class Observer {

   private String name;

   private Subject subject;

   Observer(){};

   Observer(String name,Subject subject){
      this.name=name;
      this.subject = subject;
   }
   public abstract void update();

   public String getName() {
      return name;
   }

   public void setName(String name) {
      this.name = name;
   }

   public Subject getSubject() {
      return subject;
   }

   public void setSubject(Subject subject) {
      this.subject = subject;
   }
}
  • 观察者实现(看新闻的)
package com.chou.pattern.observer;

import com.chou.pattern.subject.Subject;

/**
 * @Author Chou
 * @Description 摸鱼看实时咨询的同事
 * @ClassName NewsObserver
 * @Date 2023/5/3 12:40
 * @Version 1.0
 **/
public class NewsObserver extends Observer {

    private String name;
    private Subject subject;

    public NewsObserver() {
        super();
    }

    public NewsObserver(String name, Subject subject) {
        this.name = name;
        this.subject = subject;
    }

    @Override
    public void update() {
        System.out.println(String.format("%s,%s 关闭了其他和工作无关的网页,开始继续搬砖....", subject.getAction(), name));
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public Subject getSubject() {
        return subject;
    }

    @Override
    public void setSubject(Subject subject) {
        this.subject = subject;
    }
}
  • 观察者实现(看股市行情的)
package com.chou.pattern.observer;

import com.chou.pattern.subject.Subject;

/**
 * @Author Chou
 * @Description 看股票的同事
 * @ClassName StockOberver
 * @Date 2023/5/2 22:06
 * @Version 1.0
 **/
public class StockObserver extends Observer{

    private String name;
    private Subject subject;

    public StockObserver(){}

    public StockObserver(String name, Subject subject){
        this.name = name;
        this.subject = subject;
    }

    public void update(){
        System.out.println(String.format("%s,%s 关闭股票行情网页,继续搬砖.....", subject.getAction(), name));
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Subject getSubject() {
        return subject;
    }

    public void setSubject(Subject subject) {
        this.subject = subject;
    }
}
  • 被观察者实现(前台)
package com.chou.pattern.subject;

import com.chou.pattern.observer.Observer;
import com.chou.pattern.observer.StockObserver;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author Chou
 * @Description 主题,对应着前台
 * @ClassName Secretary
 * @Date 2023/5/2 22:15
 * @Version 1.0
 **/
public class SecretarySubject extends Subject {

    /**
     * 查看股票同事的集合
     */
    private static final List<Observer> observers = new ArrayList<>();

    // 前台小姐姐做出的动作,可以看成是一个状态的改变
    private String action;

    public SecretarySubject(){}

    public SecretarySubject(String action) {
        this.action = action;
    }

    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void delete(Observer observer) {
        observers.remove(observer);
    }

    public void notice(){
        for (Observer observer : observers) {
            observer.update();
        }
    }

    public String getAction() {
        return action;
    }

    public void setAction(String action) {
        this.action = action;
    }
}
  • client
/**
 * 使用观察者模式完成摸鱼报警系统
 * 与之前的代码相比这里把被观察者和观察者进行了抽象,针对不同的被观察者和观察者只需要继承或者实现对应类即可。
 */
private static void observerAction() {
    SecretarySubject xiaoJiang = new SecretarySubject();
    StockObserver tongShi1 = new StockObserver("老六",xiaoJiang);
    NewsObserver tongShi2 = new NewsObserver("王五",xiaoJiang);
    xiaoJiang.attach(tongShi1);
    xiaoJiang.attach(tongShi2);
    xiaoJiang.setAction("老板回来啦!!!");
    xiaoJiang.notice();
}

相较于第一种方式,这里对观察者和被观察者做了抽象。观察者和被观察者可以是任何对象,被观察者可以是前台,也可以老板等等。遵循了依赖倒转原则,面对抽象编程。从而提高了程序的扩展能力。

观察者模式的优劣

对比上面的两种实现,观察者模式好处简单来说就是降低了观察者和被观察者之间的耦合关系,使得两者之间的关系变成了抽象耦合。观察者模式的应用场景也体现出来了,当一个对象的改变需要同时改变多个对象的时候,并且不知道要有多少个对象待改变时,可以考虑使用这种方式。而观察者模式也有存在不足的地方,第一点就是在notice()方法中是循环去调用观察者对象的,这样如果观察者很多话,可能会非常的耗时。第二,当系统比较复杂时,在观察中存在循环依赖是时,会导致系统崩溃。

学无止境,对于观察模式的结构上可以进行再次优化,观察者是我们自己定义的,在实际的系统开发过程中需要用到已经封装好,定义的被观察者对象一些状态的变更需要更新其他组件中的对象,即观察者这时为(方法作用是不一样的)。就需要使用类似委派的机制去实现--事件委派模式,详情可以见 文章

观察者模式在Spring框架中的体现

在Spring Ioc 框架中事件机制和监听器就是观察者模式的体现。

监听器充当订阅者(观察者),监听特定的事件;事件源充当被观察的主体(被观察者),用来发布事件;IOC容器本身也是事件广播器,可以理解成观察者。

个人把Spring 中的事件机制分为四种核心角色:\

  • 事件源:发布事件的对象
  • 事件:事件发布的信息/做出的动作
  • 广播器:事件真正广播给监听器的对象(ApplicationContext)
    • ApplicationContext 接口有实现 ApplicationEventPublisher 接口,具备事件广播器的事件发布的能力
    • ApplicationEventMulticaster组合了所有的监听器,具备事件广播器的广播事件的能力
  • 监听器:监听事件的对象

在Spring 事件机制中主要涉及接口有:

  • ApplicationListener

    • 接口的定义
        @FunctionalInterface
        public interface ApplicationListener<E extends ApplicationEvent> extends EventListener{
    
           // 处理application 中的事件
           void onApplicationEvent(E event);
    
           // 给任意的类型数据创建一个监听器
           static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer){
              return event -> consumer.accept(event.getPayload());
           }
    
    }
    
    
  • ApplicationEvent

    • 接口的定义
       public abstract class ApplicationEvent extends EventObject {
    
          private static final long serialVersionUID = 7099057708183571937L;
    
          private final long timestamp;
          
          // 初始化事件对象,Object 可以理解为事件源
          public ApplicationEvent(Object source) {
             super(source);
             this.timestamp = System.currentTimeMillis();
          }
    
          public ApplicationEvent(Object source, Clock clock) {
             super(source);
             this.timestamp = clock.millis();
          }
    
          public final long getTimestamp() {
             return this.timestamp;
          }
    
       }
    
    • ApplicationContextEvent

    ApplicationEvent 是继承jdk原生的EventObject类,然后spring 再做了一层抽象,直接发布一件事件没有太大的意义。所以对ApplicationEvent 又做了很多扩展。使得在spring 容器内部可以使用事件机制来做很多事情。

    public abstract class ApplicationContextEvent extends ApplicationEvent { 
        public ApplicationContextEvent(ApplicationContext source) {
            super(source); 
        } 
        
        public final ApplicationContext getApplicationContext() {
            return (ApplicationContext) getSource();
        } 
    }
    

    它在构造时,会把 IOC 容器一起传进去,这意味着事件发生时,可以通过监听器直接取到 ApplicationContext 而不需要做额外的操作,这才是 SpringFramework 中事件模型扩展最值得的地方。

  • 自定义事件开发

场景:系统在用户注册后需要广播一个 注册成功 的事件。需要将用户信息带入到事件然后广播出去,列如发送 短信、邮箱、App消息提醒等等。

  1. 定义事件
package com.chou.spring.event.event;

import org.springframework.context.ApplicationEvent;

import java.io.Serializable;

/**
 * @Author Chou
 * @Description 事件继承自Application 对象
 * @ClassName RegisterEnvent
 * @Date 2023/4/20 22:39
 * @Version 1.0
 **/
public class RegisterEvent extends ApplicationEvent implements Serializable {
   public static final long serialVersionUID = -1L;


   /**
    * source 对象即需要发布的对象我们称之为事件源
    * @param source
    */
   public RegisterEvent(Object source) {
      super(source);
   }
}
  1. 定义监听器 (被观察者)

可以分为三种监听器 邮件 短信 App 消息

邮件消息监听器

package com.chou.spring.event.listener;

import com.chou.spring.event.event.RegisterEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @Author Chou
 * @Description 监听器 可以看做是观察者实现ApplicationListener<> 并需要传入事件对象
 * @ClassName EmailRegisterMessageListener
 * @Date 2023/4/20 22:44
 * @Version 1.0
 **/
@Component
public class EmailRegisterMessageListener implements ApplicationListener<RegisterEvent> {
   @Override
   public void onApplicationEvent(RegisterEvent event) {
      System.out.printf("监听到了事件。。。。。开始发送email消息");
   }
}

App消息监听器

package com.chou.spring.event.listener;

import com.chou.spring.event.event.RegisterEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @Author Chou
 * @Description TODO
 * @ClassName SmsSenderListener
 * @Date 2023/4/20 22:49
 * @Version 1.0
 **/
@Component
public class SmsSenderListener implements ApplicationListener<RegisterEvent> {
   @Override
   public void onApplicationEvent(RegisterEvent event) {
      System.out.printf("监听到了事件。。。。。开始发送sms消息");

   }
}

短息消息监听器

package com.chou.spring.event.listener;

import com.chou.spring.event.event.RegisterEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @Author Chou
 * @Description TODO
 * @ClassName MessageSenderListener
 * @Date 2023/4/20 22:50
 * @Version 1.0
 **/
@Component
public class MessageSenderListener implements ApplicationListener<RegisterEvent> {
   @Override
   public void onApplicationEvent(RegisterEvent event) {
      System.out.printf("监听到了事件。。。。。开始发送app消息");
   }
}
  1. 业务逻辑触发事件(观察者)
package com.chou.spring.event.service;

import com.chou.spring.event.event.RegisterEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;

/**
 * @Author Chou
 * @Description TODO
 * @ClassName RegisterService
 * @Date 2023/4/20 22:43
 * @Version 1.0
 **/
@Service("registerService")
public class RegisterService implements ApplicationEventPublisherAware {

   public ApplicationEventPublisher applicationEventPublisher;

   public void register(String userName){
      // 用户注册成功
      System.out.println(userName + "注册成功");
      applicationEventPublisher.publishEvent(new RegisterEvent(userName));
   }

   @Override
   public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
      this.applicationEventPublisher = applicationEventPublisher;
   }
}

总结: 所学的技能需要应用到实际开发中。观察者模式带来的解耦方式在开发过程中有很多的适用场景,在这个过程中需要反复的去考量。