• 设计模式【5】——观察者模式


    一、观察者模式

    观察者模式(英文 Observer Pattern),也叫监听模式,发布-订阅模式,属于行为型模式。当一个对象的状态或者数据更新时,依赖于该对象的其他对象也需要做出相应的更新,该模式经常应用于实现订阅功能,例如进行一次消费后,订单系统需要进行处理,需要发送短信提示,需要发送邮件提示,需要日志记录,等等,此外,Spring的事件机制也是观察者模式的应用。

    观察者模式与桥接模式有点类似,桥接模式主要是针对多对多的场景,而观察者模式主要是针对一对多的场景;此外,观察者模式涉及集合的维护,而桥接模式仅仅是接口的引用。

    二、优缺点

    • 优点:
      • 对象解耦
      • 拓展便利
    • 缺点:
      • 观察者太多时耗时久
      • 可能存在循环调用造成系统崩溃

    三、示例代码

    1、简单的观察者模式代码

    • 观察者(Observer):监听某个对象
    • 被观察者(Subject):被监听的对象
    // 被观察者接口
    public interface Subject {
        void addObserver(Observer observer);
        void removeObserver(Observer observer);
        void notifyAllObserver();
    }
    
    
    // 被观察者实现类
    import java.util.ArrayList;
    import java.util.List;
    
    public class ConcreteSubject implements Subject {
    
        private List<Observer> list = new ArrayList<>();
    
        @Override
        public void addObserver(Observer observer) {
            list.add(observer);
        }
    
        @Override
        public void removeObserver(Observer observer) {
            list.remove(observer);
        }
    
        @Override
        public void notifyAllObserver() {
            list.forEach(Observer::update);
        }
    }
    
    
    // 观察者接口
    public interface Observer {
        void update();
    }
    
    
    // 4、观察者实现类
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class ConcreteObserver implements Observer {
        private static final Logger LOGGER = LoggerFactory.getLogger(ConcreteObserver.class);
        
        private Subject subject;
    
    	// 将观察者注册进被观察者里面
        public ConcreteObserver(Subject subject) {
            this.subject = subject;
            this.subject.addObserver(this);
        }
    
        @Override
        public void update() {
            LOGGER.info("concreteObserver update.");
        }
    }
    
    // 5、模拟调用
    import org.springframework.boot.ApplicationArguments;
    import org.springframework.boot.ApplicationRunner;
    import org.springframework.stereotype.Component;
    
    @Component
    public class TestObserverPattern implements ApplicationRunner {
    
        @Override
        public void run(ApplicationArguments applicationArguments) throws Exception {
            ConcreteSubject concreteSubject = new ConcreteSubject();
            new ConcreteObserver(concreteSubject);
            concreteSubject.notifyAllObserver();
        }
    }
    
    // 6、结果展示
    2022-08-07 16:43:28.961  INFO 9296 --- [           main] c.test.observerPattern.ConcreteObserver  : concreteObserver update.
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    2、Spring 的事件机制

    // 1、自定义事件类
    import org.springframework.context.ApplicationEvent;
    import java.util.Map;
    
    public class OrderEvent extends ApplicationEvent {
    
        private String name;
    
        private Map<String,String> conf;
    
        public OrderEvent(Object source, String name, Map<String, String> conf) {
            super(source);
            this.name = name;
            this.conf = conf;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Map<String, String> getConf() {
            return conf;
        }
    
        public void setConf(Map<String, String> conf) {
            this.conf = conf;
        }
    }
    
    
    // 2、被监听者实现类
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.ApplicationArguments;
    import org.springframework.boot.ApplicationRunner;
    import org.springframework.context.ApplicationEventPublisher;
    import org.springframework.stereotype.Component;
    
    import java.util.HashMap;
    
    @Component
    public class OrderPublisher implements ApplicationRunner {
        private static final Logger LOGGER = LoggerFactory.getLogger(OrderPublisher.class);
    
        @Autowired
        private ApplicationEventPublisher applicationEventPublisher;
    
        @Override
        public void run(ApplicationArguments applicationArguments) throws Exception {
            LOGGER.info("publish order event begin.");
            OrderEvent orderEvent = new OrderEvent(this, "zhangsan", new HashMap<>());
    // 也可以通过 applicationContext.publishEvent(orderEvent) 来发布事件
            applicationEventPublisher.publishEvent(orderEvent);
            LOGGER.info("publish order event end.");
        }
    }
    
    // 3、监听者实现类(可以多个)
    // 实现 ApplicationListener 接口,传入特定类型的事件,或者使用@EventListener注解
    
    import com.test.utils.TimeUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.ApplicationListener;
    import org.springframework.stereotype.Component;
    
    @Component
    public class LogListener implements ApplicationListener<OrderEvent> {
        private static final Logger LOGGER = LoggerFactory.getLogger(LogListener.class);
    
        @Override
        public void onApplicationEvent(OrderEvent orderEvent) {
            LOGGER.info("log listener begin with [{}].", orderEvent.getName());
            TimeUtils.sleep(10000, LOGGER);
        }
    }
    
    import com.test.utils.TimeUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.ApplicationListener;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Component;
    
    @Component
    // @Async 开启异步执行,需要在启动类加上@EnableAsync注解
    public class EmailListener implements ApplicationListener<OrderEvent> {
        private static final Logger LOGGER = LoggerFactory.getLogger(EmailListener.class);
    
        @Override
        public void onApplicationEvent(OrderEvent orderEvent) {
            LOGGER.info("email listener begin with [{}].", orderEvent.getName());
            TimeUtils.sleep(20000, LOGGER);
        }
    }
    
    import com.test.utils.TimeUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.ApplicationEvent;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.event.EventListener;
    
    @Configuration
    public class OrderEventConfig {
        public static final Logger LOGGER = LoggerFactory.getLogger(OrderEventConfig.class);
    
        @EventListener(classes = {
                OrderEvent.class
        })
        // 使用 @EventListener 注解,则可以在一个类里面监听多种事件
        public void orderEventHandler(OrderEvent orderEvent) {
            LOGGER.info("order listener begin with [{}].", orderEvent.getName());
            TimeUtils.sleep(10000, LOGGER);
        }
    
        @EventListener
        public void allEventHandler(ApplicationEvent applicationEvent) {
            LOGGER.error("all event listener begin , event class is [{}].", applicationEvent.getClass().getName());
        }
    }
    
    // 4、时间工具类
    import org.slf4j.Logger;
    
    public class TimeUtils { 
        public static void sleep(int ms, Logger logger) {
            try {
                logger.info("thread sleep [{}] ms begin.", ms);
                Thread.sleep(ms);
                logger.info("thread sleep [{}] ms end.", ms);
            } catch (InterruptedException e) {
                logger.error("thread sleep error.", e);
            }
        }
    }
    
    // 5、结果展示
    2022-08-07 17:49:54.323  INFO 17576 --- [           main] c.t.o.springEvent.OrderPublisher         : publish order event begin.
    2022-08-07 17:49:54.324  INFO 17576 --- [           main] c.t.o.springEvent.EmailListener          : email listener begin with [zhangsan].
    2022-08-07 17:49:54.328  INFO 17576 --- [           main] c.t.o.springEvent.EmailListener          : thread sleep [20000] ms begin.
    2022-08-07 17:50:14.336  INFO 17576 --- [           main] c.t.o.springEvent.EmailListener          : thread sleep [20000] ms end.
    2022-08-07 17:50:14.336  INFO 17576 --- [           main] c.t.o.springEvent.LogListener            : log listener begin with [zhangsan].
    2022-08-07 17:50:14.336  INFO 17576 --- [           main] c.t.o.springEvent.LogListener            : thread sleep [10000] ms begin.
    2022-08-07 17:50:24.350  INFO 17576 --- [           main] c.t.o.springEvent.LogListener            : thread sleep [10000] ms end.
    2022-08-07 17:50:24.350  INFO 17576 --- [           main] c.t.o.springEvent.OrderPublisher         : publish order event end.
    2022-08-07 17:50:24.356  INFO 17576 --- [           main] com.test.SpringProjectApplication        : Started SpringProjectApplication in 37.643 seconds (JVM running for 38.592)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152

    如上所示是串行执行,如果要开启异步,可以通过在启动类加上@EnableAsync注解,在需要异步执行的监听类加上@Async,结果如下,异步处理的会通过线程处理,不影响主进程应用的启动:

    2022-08-07 17:57:51.265  INFO 17292 --- [           main] c.t.o.springEvent.OrderPublisher         : publish order event begin.
    2022-08-07 17:57:51.276  INFO 17292 --- [           main] .s.a.AnnotationAsyncExecutionInterceptor : No TaskExecutor bean found for async processing
    2022-08-07 17:57:51.277  INFO 17292 --- [           main] c.t.o.springEvent.LogListener            : log listener begin with [zhangsan].
    2022-08-07 17:57:51.277  INFO 17292 --- [cTaskExecutor-1] c.t.o.springEvent.EmailListener          : email listener begin with [zhangsan].
    2022-08-07 17:57:51.281  INFO 17292 --- [           main] c.t.o.springEvent.LogListener            : thread sleep [10000] ms begin.
    2022-08-07 17:57:51.282  INFO 17292 --- [cTaskExecutor-1] c.t.o.springEvent.EmailListener          : thread sleep [20000] ms begin.
    2022-08-07 17:58:01.293  INFO 17292 --- [           main] c.t.o.springEvent.LogListener            : thread sleep [10000] ms end.
    2022-08-07 17:58:01.293  INFO 17292 --- [           main] c.t.o.springEvent.OrderPublisher         : publish order event end.
    2022-08-07 17:58:01.299  INFO 17292 --- [           main] com.test.SpringProjectApplication        : Started SpringProjectApplication in 17.591 seconds (JVM running for 18.451)
    2022-08-07 17:58:11.290  INFO 17292 --- [cTaskExecutor-1] c.t.o.springEvent.EmailListener          : thread sleep [20000] ms end.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    此外,还可以使用SmartApplicationListener来设置优先级,匹配事件类型,匹配事件发布者。

    import com.test.observerPattern.springEvent.OrderEvent;
    import com.test.observerPattern.springEvent.OrderPublisher;
    import com.test.utils.TimeUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.ApplicationEvent;
    import org.springframework.context.event.SmartApplicationListener;
    import org.springframework.stereotype.Component;
    
    @Component
    public class PriorityListener implements SmartApplicationListener {
        private static final Logger LOGGER = LoggerFactory.getLogger(PriorityListener.class);
    
        @Override
        public boolean supportsEventType(Class<? extends ApplicationEvent> aClass) {
            return aClass == OrderEvent.class;
        }
    
        @Override
        public boolean supportsSourceType(Class<?> aClass) {
            return aClass == OrderPublisher.class;
        }
    
        @Override
        public void onApplicationEvent(ApplicationEvent applicationEvent) {
            LOGGER.info("priority listener begin with [{}].", ((OrderEvent) applicationEvent).getName());
            TimeUtils.sleep(10000, LOGGER);
        }
    
        @Override
        public int getOrder() {
            // 数字越小,优先级越高
            return 0;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 执行顺序:优先级为0的监听者 > 注解类型的监听者 > 实现ApplicationListener接口
  • 相关阅读:
    Mysql 索引原理和优化方式
    NLP(14)--文本匹配任务
    Babylonjs PointerEventTypes.POINTERMOVE 获取不到模型信息
    微信小程序能不能有一种公共的分包,能被普通的分包引用其资源?(内有解决方案)
    Go通过cobra快速构建命令行应用
    1057 Stack
    简单的kafka&redis学习之kafka
    看完这三招学会拼照片拼成拼图怎么拼
    TS 基础
    python (*)和(**)的用法
  • 原文地址:https://blog.csdn.net/Maslii/article/details/126213367