设计模式-观察者模式

1. 实现观察者模式的使用案例

  1. 观察者模式核心是让事件的发布者去观察消息

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

1.1 气象站的天气信息 : 将天气信息的更新消息同步到订阅了天气信息的人上

java 复制代码
package club.shengsheng.desigin.oberver;

import club.shengsheng.desigin.oberver.TVStation.TVStation;

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

// 气象站
public class WeatherStation {

    private List<User> userList = new ArrayList<User>();

    private final TVStation station;
    // 把电视台交给气象站
    public WeatherStation(TVStation station) {
            this.station = station;
    }


    public String getInfo(){
        if(new Random().nextBoolean()){
            return "晴天";
        }
        return "雨天";
    }

    // 订阅
    public void subscribe(User user){
        if(userList != null && !userList.contains(user) ){
            userList.add(user);
        }

    }

    public void start() throws Exception{
        while(true){
            String info = getInfo();
            // 一个天气更新的事件
            WeatherUpdateEvent weatherUpdateEvent = new WeatherUpdateEvent(info);
            // 电视台发布事件
            station.publish(weatherUpdateEvent);
            // 更新信息
//            station.onInfoUpdate(info);
            Thread.sleep(300);
        }

    }

}

1.2 电视台的总线系统 ,事件发布器 , 监听消息同步消息到User中。

接收到新的天气信息的函数

java 复制代码
package club.shengsheng.desigin.oberver.TVStation;

import club.shengsheng.desigin.oberver.Event;
import club.shengsheng.desigin.oberver.EventListener;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/// 电视台
/// 消息总线
public class TVStation {
    // 发布订阅模型
    List<EventListener> enventList = new ArrayList<EventListener>();
    //  监听器依次遍历
    private final Map<Class<? extends Event>,List<EventListener>> eventListenerMap = new HashMap<>();

    // 电视台的订阅的逻辑  消息的监听
    public void subscribe(EventListener listener,Class<? extends Event> eventClazz) {
        eventListenerMap.computeIfAbsent(eventClazz, k -> new ArrayList<>()).add(listener);
    }

    // 发布逻辑实现
    public void publish(Event event){
        Class<? extends Event> aClass = event.getClass();
        // 拿到监听器
        List<EventListener> eventListeners = eventListenerMap.get(aClass);

        if(eventListeners != null){
            //发布给对应的监听器
            enventList.forEach(listener -> listener.onEvent(event) );
        }

        for (EventListener eventListener : enventList) {
            eventListener.onEvent(event);
        }
    }




}

1.3 天气更新事件,使用event 实现 事件的抽象,有具体的事件继承Event方法就行 ,进行事件的

更新

java 复制代码
// 事件接口
    public interface Event {


       // 时间戳获取函数
        long timestamp();

        // 携带的内容
        Object source();

    }

1.4 实现消息的监听器 ,消息的消费者要实现这个监听器,监听消息的逻辑,并实现具体的拿到消息后的方法

java 复制代码
// 监听消息的人
public interface EventListener {

    // 消息监听
    void onEvent(Event event);

}
    

Main函数中使用具体的方法

java 复制代码
package club.shengsheng.desigin.oberver;

import club.shengsheng.desigin.oberver.TVStation.TVStation;
// 设计模式的本质是让程序更好理解,更好维护,更好扩展 而不去关注 具体用的是那个设计模式
public class Main {
    // 观察者模式 -》 事件的生产者 直接去监听器
    // 发布订阅 -> 事件的生产者 -》 总线 -》 监听器
    //
    public static void main(String[] args) {
        // Tv是 时间的发布者
        TVStation tvStation = new TVStation();
        WeatherStation station = new WeatherStation(tvStation);
        // 事件驱动的发布订阅模型
        //
        User tom = new User("tom",info->{
            if( info.equals("晴天") ){
                System.out.println("看日出");
            }else{
                System.out.println("看不了日出 , 呆在家里");
            }
        });

        // 点击实现
        User jerry = new User("jerry", info -> {
            if (info.equals("阴天")) {
                System.out.println("阴天 在家玩游戏");
            }
        });
        /// 定义监听器  以及 关心的事件 只关心天气的更新事件
        tvStation.subscribe(tom, WeatherUpdateEvent.class );
        tvStation.subscribe(jerry,WeatherUpdateEvent.class);

        try {
            station.start();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

}

2. Spring中的观察者模式的使用

2.1 Spring中的Event 实现类继承 JDK中的实现类

java 复制代码
package org.springframework.context;

import java.time.Clock;
import java.util.EventObject;

/**
 * Class to be extended by all application events. Abstract as it
 * doesn't make sense for generic events to be published directly.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @see org.springframework.context.ApplicationListener
 * @see org.springframework.context.event.EventListener
 */
public abstract class ApplicationEvent extends EventObject {

	/** use serialVersionUID from Spring 1.2 for interoperability. */
	private static final long serialVersionUID = 7099057708183571937L;

	/** System time when the event happened. */
	private final long timestamp;


	/**
	 * Create a new {@code ApplicationEvent} with its {@link #getTimestamp() timestamp}
	 * set to {@link System#currentTimeMillis()}.
	 * @param source the object on which the event initially occurred or with
	 * which the event is associated (never {@code null})
	 * @see #ApplicationEvent(Object, Clock)
	 */
	public ApplicationEvent(Object source) {
		super(source);
		this.timestamp = System.currentTimeMillis();
	}

	/**
	 * Create a new {@code ApplicationEvent} with its {@link #getTimestamp() timestamp}
	 * set to the value returned by {@link Clock#millis()} in the provided {@link Clock}.
	 * <p>This constructor is typically used in testing scenarios.
	 * @param source the object on which the event initially occurred or with
	 * which the event is associated (never {@code null})
	 * @param clock a clock which will provide the timestamp
	 * @since 5.3.8
	 * @see #ApplicationEvent(Object)
	 */
	public ApplicationEvent(Object source, Clock clock) {
		super(source);
		this.timestamp = clock.millis();
	}


	/**
	 * Return the time in milliseconds when the event occurred.
	 * @see #ApplicationEvent(Object)
	 * @see #ApplicationEvent(Object, Clock)
	 */
	public final long getTimestamp() {
		return this.timestamp;
	}

}

2.2 Spring中的消息总线,事件发布器,ApplicationEventPublisher 只有两个功能: 1是发布 2是注册。

java 复制代码
package org.springframework.context;

/**
 * Interface that encapsulates event publication functionality.
 *
 * <p>Serves as a super-interface for {@link ApplicationContext}.
 *
 * @author Juergen Hoeller
 * @author Stephane Nicoll
 * @since 1.1.1
 * @see ApplicationContext
 * @see ApplicationEventPublisherAware
 * @see org.springframework.context.ApplicationEvent
 * @see org.springframework.context.event.ApplicationEventMulticaster
 * @see org.springframework.context.event.EventPublicationInterceptor
 * @see org.springframework.transaction.event.TransactionalApplicationListener
 */
@FunctionalInterface
public interface ApplicationEventPublisher {

	/**
	 * Notify all <strong>matching</strong> listeners registered with this
	 * application of an application event. Events may be framework events
	 * (such as ContextRefreshedEvent) or application-specific events.
	 * <p>Such an event publication step is effectively a hand-off to the
	 * multicaster and does not imply synchronous/asynchronous execution
	 * or even immediate execution at all. Event listeners are encouraged
	 * to be as efficient as possible, individually using asynchronous
	 * execution for longer-running and potentially blocking operations.
	 * <p>For usage in a reactive call stack, include event publication
	 * as a simple hand-off:
	 * {@code Mono.fromRunnable(() -> eventPublisher.publishEvent(...))}.
	 * As with any asynchronous execution, thread-local data is not going
	 * to be available for reactive listener methods. All state which is
	 * necessary to process the event needs to be included in the event
	 * instance itself.
	 * <p>For the convenient inclusion of the current transaction context
	 * in a reactive hand-off, consider using
	 * {@link org.springframework.transaction.reactive.TransactionalEventPublisher#publishEvent(Function)}.
	 * For thread-bound transactions, this is not necessary since the
	 * state will be implicitly available through thread-local storage.
	 * @param event the event to publish
	 * @see #publishEvent(Object)
	 * @see ApplicationListener#supportsAsyncExecution()
	 * @see org.springframework.context.event.ContextRefreshedEvent
	 * @see org.springframework.context.event.ContextClosedEvent
	 */
	default void publishEvent(ApplicationEvent event) {
		publishEvent((Object) event);
	}

	/**
	 * Notify all <strong>matching</strong> listeners registered with this
	 * application of an event.
	 * <p>If the specified {@code event} is not an {@link ApplicationEvent},
	 * it is wrapped in a {@link PayloadApplicationEvent}.
	 * <p>Such an event publication step is effectively a hand-off to the
	 * multicaster and does not imply synchronous/asynchronous execution
	 * or even immediate execution at all. Event listeners are encouraged
	 * to be as efficient as possible, individually using asynchronous
	 * execution for longer-running and potentially blocking operations.
	 * <p>For the convenient inclusion of the current transaction context
	 * in a reactive hand-off, consider using
	 * {@link org.springframework.transaction.reactive.TransactionalEventPublisher#publishEvent(Object)}.
	 * For thread-bound transactions, this is not necessary since the
	 * state will be implicitly available through thread-local storage.
	 * @param event the event to publish
	 * @since 4.2
	 * @see #publishEvent(ApplicationEvent)
	 * @see PayloadApplicationEvent
	 */
	void publishEvent(Object event);

}

2.3 ApplicationEventPublisher 发布的事件会发送给事件广器 ApplicationEventMulticaster,ApplicationEventMulticaster接口中的MulticastEventer方法实现真正的广播机制。广播我们的事件, ApplicationEventMulticaster实现的事件的广播机制实际上是为了让Spring的事件发布更具有托展性,Spring中的事件发布器默认是串行的,当ApplicationEventMulticaster实现事件的广播的功能后开发者可自定义事件的发布,也就支持了事件的异步发布。

java 复制代码
/*
 * Copyright 2002-2023 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context.event;

import java.util.function.Predicate;

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;

/**
 * Interface to be implemented by objects that can manage a number of
 * {@link ApplicationListener} objects and publish events to them.
 *
 * <p>An {@link org.springframework.context.ApplicationEventPublisher}, typically
 * a Spring {@link org.springframework.context.ApplicationContext}, can use an
 * {@code ApplicationEventMulticaster} as a delegate for actually publishing events.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @author Stephane Nicoll
 * @see ApplicationListener
 */
public interface ApplicationEventMulticaster {

	/**
	 * Add a listener to be notified of all events.
	 * @param listener the listener to add
	 * @see #removeApplicationListener(ApplicationListener)
	 * @see #removeApplicationListeners(Predicate)
	 */
	void addApplicationListener(ApplicationListener<?> listener);

	/**
	 * Add a listener bean to be notified of all events.
	 * @param listenerBeanName the name of the listener bean to add
	 * @see #removeApplicationListenerBean(String)
	 * @see #removeApplicationListenerBeans(Predicate)
	 */
	void addApplicationListenerBean(String listenerBeanName);

	/**
	 * Remove a listener from the notification list.
	 * @param listener the listener to remove
	 * @see #addApplicationListener(ApplicationListener)
	 * @see #removeApplicationListeners(Predicate)
	 */
	void removeApplicationListener(ApplicationListener<?> listener);

	/**
	 * Remove a listener bean from the notification list.
	 * @param listenerBeanName the name of the listener bean to remove
	 * @see #addApplicationListenerBean(String)
	 * @see #removeApplicationListenerBeans(Predicate)
	 */
	void removeApplicationListenerBean(String listenerBeanName);

	/**
	 * Remove all matching listeners from the set of registered
	 * {@code ApplicationListener} instances (which includes adapter classes
	 * such as {@link ApplicationListenerMethodAdapter}, for example, for annotated
	 * {@link EventListener} methods).
	 * <p>Note: This just applies to instance registrations, not to listeners
	 * registered by bean name.
	 * @param predicate the predicate to identify listener instances to remove,
	 * for example, checking {@link SmartApplicationListener#getListenerId()}
	 * @since 5.3.5
	 * @see #addApplicationListener(ApplicationListener)
	 * @see #removeApplicationListener(ApplicationListener)
	 */
	void removeApplicationListeners(Predicate<ApplicationListener<?>> predicate);

	/**
	 * Remove all matching listener beans from the set of registered
	 * listener bean names (referring to bean classes which in turn
	 * implement the {@link ApplicationListener} interface directly).
	 * <p>Note: This just applies to bean name registrations, not to
	 * programmatically registered {@code ApplicationListener} instances.
	 * @param predicate the predicate to identify listener bean names to remove
	 * @since 5.3.5
	 * @see #addApplicationListenerBean(String)
	 * @see #removeApplicationListenerBean(String)
	 */
	void removeApplicationListenerBeans(Predicate<String> predicate);

	/**
	 * Remove all listeners registered with this multicaster.
	 * <p>After a remove call, the multicaster will perform no action
	 * on event notification until new listeners are registered.
	 * @see #removeApplicationListeners(Predicate)
	 */
	void removeAllListeners();

	/**
	 * Multicast the given application event to appropriate listeners.
	 * <p>Consider using {@link #multicastEvent(ApplicationEvent, ResolvableType)}
	 * if possible as it provides better support for generics-based events.
	 * <p>If a matching {@code ApplicationListener} does not support asynchronous
	 * execution, it must be run within the calling thread of this multicast call.
	 * @param event the event to multicast
	 * @see ApplicationListener#supportsAsyncExecution()
	 */
	void multicastEvent(ApplicationEvent event);

	/**
	 * Multicast the given application event to appropriate listeners.
	 * <p>If the {@code eventType} is {@code null}, a default type is built
	 * based on the {@code event} instance.
	 * <p>If a matching {@code ApplicationListener} does not support asynchronous
	 * execution, it must be run within the calling thread of this multicast call.
	 * @param event the event to multicast
	 * @param eventType the type of event (can be {@code null})
	 * @since 4.2
	 * @see ApplicationListener#supportsAsyncExecution()
	 */
	void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);

}
2. 4 SpringBoot中实现事件的发布
java 复制代码
// 定义一个事件的类 继承 spring的ApplicationEvent
public class RandomEvent extends ApplicationEvent {

    public RandomEvent(Object source) {
        super(source);

    }
}

// 定义一个监听器
import org.example.controller.event.RandomEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

// 事件监听器
@Component
public class RandomEventListener {

    // 处理一个事件
    @EventListener
    public void onRandomEvent(RandomEvent randomEvent) {
        // 处理随机事件的内容
        System.out.println(randomEvent.getSource());
        System.out.println("Random event triggered");

    }
}



// 初始化成功后实现事件的发布
@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(Main.class, args);
        context.publishEvent( new RandomEvent("login 初始化了"));

    }
}



// 结果是SpringBoot启动后会打印"Random event triggered"

2.5 发布订阅模式的具体运用

2.5.1 实现一个注册的事件

java 复制代码
package org.example.controller.listener;

import org.springframework.context.ApplicationEvent;

// 注册事件 
public class RegisterEvent extends ApplicationEvent {


    public RegisterEvent(Object user) {
        super(user);
    }

    public String getUser() {
        return getSource().toString();
    }
}

2.5.2 在接口逻辑中当注册成功后发布这个注册事件

java 复制代码
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.example.controller.event.RandomEvent;
import org.example.controller.listener.RegisterEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Random;

@RestController
@RequestMapping("/user")
public class LoginController {

    @Resource
    private ApplicationContext applicationContext;

    @Resource // Spring 事件发布
    private ApplicationEventPublisher publisher;

    @Resource  // Spring 事件广播
    private ApplicationEventPublisher multicaster;


    // 设计模式解耦
    @GetMapping("/register")
    public String register( @RequestParam("user") String user){

        System.out.println(user +" is register");
        //发布事件
        publisher.publishEvent(new RegisterEvent(user));
        System.out.println("打印日志");
        return "success";
    }
 
}

2.5.2 实现事件的监听,并执行具体的业务逻辑

java 复制代码
import org.example.controller.listener.RegisterEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

@Service
public class MailService {

    @EventListener
    public void onRegister(RegisterEvent event) {
        String user = event.getUser();
        System.out.println("给 "+user+" 发了邮件 mail" );

    }
}
java 复制代码
import org.example.controller.listener.RegisterEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

// 礼包服务
@Service
public class GiftService {


    @EventListener // 注册事件
    public void onRegisterEvent(RegisterEvent event) {
        String source = event.getUser();
        System.out.println( "给 " + source  + "发了新手礼包");

    }
}

调用接口后实现的打印结果

相关推荐
然我23 分钟前
react-router-dom 完全指南:从零实现动态路由与嵌套布局
前端·react.js·面试
找了一圈尾巴27 分钟前
设计模式(结构型)-适配器模式
设计模式·适配器模式
哪里不会点哪里.29 分钟前
适配器模式:兼容不兼容接口
java·开发语言
一_个前端31 分钟前
Vite项目中SVG同步转换成Image对象
前端
202632 分钟前
12. npm version方法总结
前端·javascript·vue.js
vvilkim32 分钟前
单例模式详解:确保一个类只有一个实例
单例模式·设计模式
用户876128290737433 分钟前
mapboxgl中对popup弹窗添加事件
前端·vue.js
何中应34 分钟前
Maven项目没有Maven工具,IDEA没有识别到该项目是Maven项目怎么办?
java·后端·maven·intellij-idea
帅夫帅夫34 分钟前
JavaScript继承探秘:从原型链到ES6 Class
前端·javascript
a别念m34 分钟前
HTML5 离线存储
前端·html·html5