设计模式【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.

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)

如上所示是串行执行,如果要开启异步,可以通过在启动类加上@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.

此外,还可以使用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;
    }
}
  • 执行顺序:优先级为0的监听者 > 注解类型的监听者 > 实现ApplicationListener接口

版权声明:本文为Maslii原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。