设计模式之美笔记 —— 观察者模式

目录

一、观察者模式:

1、同步阻塞的实现方式:

2、利用Spring ApplicationListener实现异步非阻塞

二、根据应用场景的不同,观察者模式会对应不同的代码实现方式:

三、异步非阻塞的观察者模式

Guava EventBus框架:支持异步非阻塞模式,同时也支持同步阻塞模式

四、跨进程的观察者模式 - 消息队列


设计模式之美 - 56

一、观察者模式:

在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。

1、同步阻塞的实现方式:

比如:当用户注册成功以后,我们需要为用户发放:感谢信,优惠券, 体验金 等等,注册的后续操作频繁修改。这种情况下就需要用到观察者模式。

如以下代码,该种实现方式属于 。
观察者和被观察者代码在同一个线程内执行,被观察者一直阻塞,直到所有的观察者代码都执行完成之后,才执行后续的代码。对照上面讲到的用户注册的例子,register() 函数依次调用执行每个观察者的 handleRegSuccess() 函数,等到都执行完成之后,才会返回结果给客户端。

public interface RegObserver {
  void handleRegSuccess(long userId);
}

public class RegPromotionObserver implements RegObserver {
  private PromotionService promotionService; // 依赖注入

  @Override
  public void handleRegSuccess(long userId) {
    promotionService.issueNewUserExperienceCash(userId);
  }
}

public class RegNotificationObserver implements RegObserver {
  private NotificationService notificationService;

  @Override
  public void handleRegSuccess(long userId) {
    notificationService.sendInboxMessage(userId, "Welcome...");
  }
}

public class UserController {
  private UserService userService; // 依赖注入
  private List<RegObserver> regObservers = new ArrayList<>();

  // 一次性设置好,之后也不可能动态的修改
  public void setRegObservers(List<RegObserver> observers) {
    regObservers.addAll(observers);
  }

  public Long register(String telephone, String password) {
    //省略输入参数的校验代码
    //省略userService.register()异常的try-catch代码
    long userId = userService.register(telephone, password);

    for (RegObserver observer : regObservers) {
      observer.handleRegSuccess(userId);
    }

    return userId;
  }
}

2、利用Spring ApplicationListener实现异步非阻塞

ApplicationContext事件机制是观察者设计模式的实现,通过ApplicationEvent类或ApplicationListener接口,可以实现ApplicationContext事件处理。

参考文章

// 发布事件
public void publishEvent(Object resource){
        if (resource == null) return ;
        threadPoolConfigure.getCustomThreadPool().start(() -> {
            log.info("异步线程启动,resource-{}", resource);
            try {
                publish.publishEvent(new EventObject(resource));
            } catch (Exception e) {
                log.error("执行任务失败,异常信息-{}", e);
            }
        });}}


// 监听事件
public class EventListener implements ApplicationListener<EventObject> {

    @Override
    public void onApplicationEvent(EventObject bizEventObject) {
        log.info("监听到消息");
        Object source = bizEventObject.getSource();
        if (source==null) {
            log.error("发布事件值为null");
            return ;
        }
        if (source instanceof BizEventDO){

         bizEventManager.bizHandle(BizEventDO.class.cast(source));
        } else if(source instanceof UserChangeEventDO){
            //用户事件处理
            userEventManager.bizProcess(UserChangeEventDO.class.cast(source));
        } else if(source instanceof CustomerMessageEventDO){
            //推送客户消息
           customerMessageEventContext.singletonPush(CustomerMessageEventDO.class.cast(source));
        } else if(source instanceof UserLoginInfoDTO){
            //保存用户登录信息
            userLoginInfoService.saveLoginInfo(UserLoginInfoDTO.class.cast(source));
        } else if(source instanceof AccountEventDO){
            //账户变更事件
            accountEventContext.process(AccountEventDO.class.cast(source));
        }
    }
}

二、根据应用场景的不同,观察者模式会对应不同的代码实现方式:

  1. 同步阻塞的实现方式;同步阻塞是最经典的实现方式,主要是为了代码解耦;
  2. 异步非阻塞的实现方式;异步非阻塞除了能实现代码解耦之外,还能提高代码的执行效率;
  3. 进程内的实现方式;
  4. 跨进程的实现方式;进程间的观察者模式解耦更加彻底,一般是基于消息队列来实现,用来实现不同进程间的被观察者和观察者之间的交互。

三、异步非阻塞的观察者模式

基于以上的以上的实现方式,可以通过两种修改,使其变成异步非阻塞的观察者模式

  1. 在每个 handleRegSuccess() 函数中创建一个新的线程执行代码逻辑; 存在频繁创建和销毁线程,比较耗时,并且无法控制并发线程数,创建过多时可能到时堆栈溢出
  2. 在 UserController 的 register() 函数中使用线程池来执行每个观察者的 handleRegSuccess() 函数。尽管利用了线程池解决了第一种实现方式的问题,但线程池、异步执行逻辑都耦合在了 register() 函数中,增加了这部分业务代码的维护成本。
// 第一种实现方式,其他类代码不变,就没有再重复罗列
public class RegPromotionObserver implements RegObserver {
  private PromotionService promotionService; // 依赖注入

  @Override
  public void handleRegSuccess(Long userId) {
    Thread thread = new Thread(new Runnable() {
      @Override
      public void run() {
        promotionService.issueNewUserExperienceCash(userId);
      }
    });
    thread.start();
  }
}

// 第二种实现方式,其他类代码不变,就没有再重复罗列
public class UserController {
  private UserService userService; // 依赖注入
  private List<RegObserver> regObservers = new ArrayList<>();
  private Executor executor;

  public UserController(Executor executor) {
    this.executor = executor;
  }

  public void setRegObservers(List<RegObserver> observers) {
    regObservers.addAll(observers);
  }

  public Long register(String telephone, String password) {
    //省略输入参数的校验代码
    //省略userService.register()异常的try-catch代码
    long userId = userService.register(telephone, password);

    for (RegObserver observer : regObservers) {
      executor.execute(new Runnable() {
        @Override
        public void run() {
          observer.handleRegSuccess(userId);
        }
      });
    }

    return userId;
  }
}

Guava EventBus框架:支持异步非阻塞模式,同时也支持同步阻塞模式

1、EventBus、AsyncEventBus

EventBus 实现了同步阻塞的观察者模式
AsyncEventBus 继承自 EventBus,提供了异步非阻塞的观察者模式。
具体的使用方式:

EventBus eventBus = new EventBus(); // 同步阻塞模式
EventBus eventBus = new AsyncEventBus(Executors.newFixedThreadPool(8));// 异步阻塞模式

2、register() 函数

EventBus 类提供了 register() 函数用来注册观察者。

3、unregister() 函数

相对于 register() 函数,unregister() 函数用来从 EventBus 中删除某个观察者。

4、post() 函数

EventBus 类提供了 post() 函数,用来给观察者发送消息。

public void post(Object event);

跟经典的观察者模式的不同之处在于,当我们调用 post() 函数发送消息的时候,并非把消息发送给所有的观察者,而是发送给可匹配的观察者。所谓可匹配指的是,能接收的消息类型是发送消息(post 函数定义中的 event)类型的父类。

举例:AObserver 能接收的消息类型是 XMsg,BObserver 能接收的消息类型是 YMsg,CObserver 能接收的消息类型是 ZMsg。其中,XMsg 是 YMsg 的父类。

XMsg xMsg = new XMsg();
YMsg yMsg = new YMsg();
ZMsg zMsg = new ZMsg();
post(xMsg); => AObserver接收到消息
post(yMsg); => AObserver、BObserver接收到消息
post(zMsg); => CObserver接收到消息

5、@Subscribe 注解

EventBus 通过 @Subscribe 注解来标明,某个函数能接收哪种类型的消息。

public DObserver {
  //...省略其他属性和方法...
  
  @Subscribe
  public void f1(PMsg event) { //... }
  
  @Subscribe
  public void f2(QMsg event) { //... }
}

通過eventBus实现之前的 观察者模式

public class UserController {
  private UserService userService; // 依赖注入

  private EventBus eventBus;
  private static final int DEFAULT_EVENTBUS_THREAD_POOL_SIZE = 20;

  public UserController() {
    //eventBus = new EventBus(); // 同步阻塞模式
    eventBus = new AsyncEventBus(Executors.newFixedThreadPool(DEFAULT_EVENTBUS_THREAD_POOL_SIZE)); // 异步非阻塞模式
  }

  public void setRegObservers(List<Object> observers) {
    for (Object observer : observers) {
      eventBus.register(observer); // 用来注册观察者
    }
  }

  public Long register(String telephone, String password) {
    //省略输入参数的校验代码
    //省略userService.register()异常的try-catch代码
    long userId = userService.register(telephone, password);
    eventBus.post(userId); //用来给观察者发送消息
    return userId;
  }
}

public class RegPromotionObserver {
  private PromotionService promotionService; // 依赖注入

  @Subscribe  // 注釋該方法可以接受long 類型的消息
  public void handleRegSuccess(Long userId) {
    promotionService.issueNewUserExperienceCash(userId);
  }
}

public class RegNotificationObserver {
  private NotificationService notificationService;

  @Subscribe // 注釋該方法可以接受long 類型的消息
  public void handleRegSuccess(Long userId) {
    notificationService.sendInboxMessage(userId, "...");
  }
}

四、跨进程的观察者模式 - 消息队列

需要引入中間系統:消息隊列

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值