• 设计模式之观察者模式实战


    本文原文链接地址:http://nullpointer.pw/design-patterns-observer.html

    本文介绍观察者模式在项目中的实际应用。

    类型:行为型模式

    意图:一对多关系依赖的多个对象,当一个对象状态发生改变,所有依赖的对象都可以得到通知并自动更新

    主要解决:降低对象间的关联依赖性

    观察者模式也称为发布订阅模式,监听器模式。

    设计模式系列文章目录

    角色

    抽象主题(Subject)角色:抽象主题角色提供维护一个观察者对象聚集的操作方法,对聚集的增加、删除等。
    具体主题(ConcreteSubject)角色:将有关状态存入具体的观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色负责实现抽象主题中聚集的管理方法。
    抽象观察者(Observer)角色:为具体观察者提供一个更新接口。
    具体观察者(ConcreteObserver)角色:存储与主题相关的自洽状态,实现抽象观察者提供的更新接口。

    UML

    Java 提供观察者模式的支持

    一般在真实项目之中,不会完全手动实现一个观察者模式,因为在 JAVA 语言的 java.util 库里面,已经提供了一个 Observable 类以及一个 Observer 接口,构成 JAVA 语言对观察者模式的支持。直接使用提供的 util 即可。

    public class ConcreteObserver implements Observer {
        @Override
        public void update(Observable o, Object arg) {
            System.out.println("接收到更新");
        }
    }
    
    public class ConcreteSubject extends Observable {
    
        public void change() {
            setChanged();
            this.notifyObservers();
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            ConcreteObserver concreteObserver = new ConcreteObserver();
            ConcreteSubject subject = new ConcreteSubject();
            subject.addObserver(concreteObserver);
            subject.change();
        }
    }
    

    实战

    除了 Java 自身 提供的观察者模式支持外,Guava 也基于观察者模式实现的 生产/消费模型,在使用上,比 Observable 相对简单,如果需要订阅消息只需要在方法上添加 @Subscribe 注解即可。使用 EventBus 的 post 方法分发事件给消费者。

    本文以用户注册为例,当用户注册完成后,之后可能会有一系列的耗时操作,比如发送消息通知,同步数据到缓存等操作。为了给用户提供好的体验,这里使用 EventBus 来进行异步化。

    定义抽象主题

    public abstract class AbstractProducer<T> {
        public static final AsyncEventBus eventBus = new AsyncEventBus("_event_async_", Executors.newFixedThreadPool(4));
    
        public void registerAsyncEvent(EventConsumer consumer) {
            eventBus.register(consumer);
        }
    
        public abstract void post(T event);
    }
    

    定义抽象观察者

    public interface EventConsumer<T> {
        void consume(T event);
    }
    

    封装 Event 事件对象

    Event 事件对象用于事件宣发时参数传递使用。

    @Data
    public class UserRegisterEvent {
        private UserDto userDto;
    }
    
    @Data
    @ToString
    @AllArgsConstructor
    public class UserDto {
        private Long id;
        private String name;
        private LocalDateTime registerTime;
    }
    

    定义具体的主题与观察者对象

    // 主题对象,提供注册主题方法
    @Component
    public class UserRegisterProducer extends AbstractProducer<UserRegisterEvent> {
    
        @Override
        public void post(UserRegisterEvent event) {
            eventBus.post(event);
        }
    }
    
    // 观察者对象,监听主题事件
    @Component
    public class UserRegisterNotifyConsumer implements EventConsumer<UserRegisterEvent>,
            InitializingBean {
    
        @Resource
        private UserRegisterProducer userRegisterProducer;
    
        @Override
        @Subscribe // 监听事件
        public void consume(UserRegisterEvent event) {
            System.out.println("接收到用户注册事件,开始推送通知");
            System.out.println(event);
            System.out.println("接收到用户注册事件,通知推送完毕");
        }
    
        @Override
        public void afterPropertiesSet() {
            userRegisterProducer.registerAsyncEvent(this);
        }
    }
    
    // 观察者对象,监听主题事件
    @Component
    public class UserRegisterSyncCacheConsumer implements EventConsumer<UserRegisterEvent>,
            InitializingBean {
    
        @Resource
        private UserRegisterProducer userRegisterProducer;
    
        @Override
        public void afterPropertiesSet() {
            userRegisterProducer.registerAsyncEvent(this);
        }
    
        @Override
        @Subscribe // 监听事件
        public void consume(UserRegisterEvent event) {
            System.out.println("接收到用户注册事件,开始同步 Cache");
            System.out.println(event.getUserDto());
            System.out.println("接收到用户注册事件,同步 Cache 完毕");
        }
    }
    

    注意,观察者对象需要在类进行实例化的时候,进行注册事件,所以实现了 InitializingBean 接口,监听事件消息,需要在监听方法上添加 @Subscribe 注解。

    测试

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class UserRegisterProducerTest {
    
        @Resource
        private UserRegisterProducer userRegisterProducer;
        @Test
        public void post() {
            UserRegisterEvent event = new UserRegisterEvent();
            event.setUserDto(new UserDto(1L, "张三", LocalDateTime.now()));
            userRegisterProducer.post(event);
        }
    }
    

    结果输出:

    接收到用户注册事件,开始推送通知
    接收到用户注册事件,开始同步 Cache
    UserDto(id=1, name=张三, registerTime=2020-03-05T22:12:26.386)
    接收到用户注册事件,同步 Cache 完毕
    UserRegisterEvent(userDto=UserDto(id=1, name=张三, registerTime=2020-03-05T22:12:26.386))
    接收到用户注册事件,通知推送完毕
    

    因为观察者模式是无法控制消费顺序的,可能每次的输出结果都是不一致的。

    示例代码

    参考

  • 相关阅读:
    websocket实现简单的两端双向实时通信
    Carla自动驾驶仿真模拟器基本概念
    vue出镜率比较高的十大面试题
    solidity开发3类型1
    分苹果
    批清除VC临时文件
    ORM框架SQLAchemy的使用
    Yapi————安装部署
    C#中的语句
    C#中的类型转换
  • 原文地址:https://www.cnblogs.com/vcmq/p/12542382.html
Copyright © 2020-2023  润新知