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

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

本文原文链接地址: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))
接收到用户注册事件,通知推送完毕

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

示例代码

参考