• 设计模式-10-观察者模式


           经典的设计模式有23种,但是常用的设计模式一般情况下不会到一半,我们就针对一些常用的设计模式进行一些详细的讲解和分析,方便大家更加容易理解和使用设计模式。

          23种经典的设计模式分为三类:创建型、结构型、行为型。前面我们已经学习了创建型和结构型,从今天起,我们开始学习行为型设计模式。创建型设计模式主要解决“对象的创建”问题,结构型设计模式主要解决“类或对象的组合或组装”问题,那行为型设计模式主要解决的就是“类或对象之间的交互”问题。

           行为型设计模式比较多,有11个,几乎占了23种经典设计模式的一半。它们分别是:观察者模式、模板模式、策略模式、职责链模式、状态模式、迭代器模式、访问者模式、备忘录模式、命令模式、解释器模式、中介模式。

    1-观察者模式原理

           观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subscribe Design Pattern)。在GoF的《设计模式》一书中,它的定义是这样的:
    Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
           翻译成中文就是:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。

    2-观察者设计模式示例

           假设我们在开发一个P2P投资理财系统,用户注册成功之后,我们会给用户发放投资体验金。代码实现大致是下面这个样子的:

    1. public class UserController {
    2. private UserService userService; // 依赖注入
    3. private PromotionService promotionService; // 依赖注入
    4. public Long register(String telephone, String password) {
    5. //省略输入参数的校验代码
    6. //省略userService.register()异常的try-catch代码
    7. long userId = userService.register(telephone, password);
    8. promotionService.issueNewUserExperienceCash(userId);
    9. return userId;
    10. }

           虽然注册接口做了两件事情,注册和发放体验金,违反单一职责原则,但是,如果没有扩展和修改的需求,现在的代码实现是可以接受的。如果非得用观察者模式,就需要引入更多的类和更加复杂的代码结构,反倒是一种过度设计。

           相反,如果需求频繁变动,比如,用户注册成功之后,不再发放体验金,而是改为发放优惠券,并且还要给用户发送一封“欢迎注册成功”的站内信。这种情况下,我们就需要频繁地修改register()函数中的代码,违反开闭原则。而且,如果注册成功之后需要执行的后续操作越来越多,那register()函数的逻辑会变得越来越复杂,也就影响到代码的可读性和可维护性。

         这个时候,观察者模式就能派上用场了。利用观察者模式,我对上面的代码进行了重构。

    1. public interface RegObserver {
    2. void handleRegSuccess(long userId);
    3. }
    4. public class RegPromotionObserver implements RegObserver {
    5. private PromotionService promotionService; // 依赖注入
    6. @Override
    7. public void handleRegSuccess(long userId) {
    8. promotionService.issueNewUserExperienceCash(userId);
    9. }
    10. }
    11. public class RegNotificationObserver implements RegObserver {
    12. private NotificationService notificationService;
    13. @Override
    14. public void handleRegSuccess(long userId) {
    15. notificationService.sendInboxMessage(userId, "Welcome...");
    16. }
    17. }
    18. public class UserController {
    19. private UserService userService; // 依赖注入
    20. private List regObservers = new ArrayList<>();
    21. // 一次性设置好,之后也不可能动态的修改
    22. public void setRegObservers(List observers) {
    23. regObservers.addAll(observers);
    24. }
    25. public Long register(String telephone, String password) {
    26. //省略输入参数的校验代码
    27. //省略userService.register()异常的try-catch代码
    28. long userId = userService.register(telephone, password);
    29. for (RegObserver observer : regObservers) {
    30. observer.handleRegSuccess(userId);
    31. }
    32. return userId;
    33. }
    34. }

    3-异步非阻塞方式

          以上的实现方式,它是一种同步阻塞的实现方式。观察者和被观察者代码在同一个线程内执行,被观察者一直阻塞,直到所有的观察者代码都执行完成之后,才执行后续的代码。对照上面讲到的用户注册的例子,register()函数依次调用执行每个观察者的handleRegSuccess()函数,等到都执行完成之后,才会返回结果给客户端。如果要求对接口性能比较高,希望接口的响应时间尽可能短,那我们可以将同步阻塞的实现方式改为异步非阻塞的实现方式,以此来减少响应时间。

    我们可以采用异步方式进行,我们可以采用java的新创建线程来改造:

    1. // 第一种实现方式,其他类代码不变,就没有再重复罗列
    2. public class RegPromotionObserver implements RegObserver {
    3. private PromotionService promotionService; // 依赖注入
    4. @Override
    5. public void handleRegSuccess(long userId) {
    6. Thread thread = new Thread(new Runnable() {
    7. @Override
    8. public void run() {
    9. promotionService.issueNewUserExperienceCash(userId);
    10. }
    11. });
    12. thread.start();
    13. }
    14. }
    15. // 第二种实现方式,其他类代码不变,就没有再重复罗列
    16. public class UserController {
    17. private UserService userService; // 依赖注入
    18. private List regObservers = new ArrayList<>();
    19. private Executor executor;
    20. public UserController(Executor executor) {
    21. this.executor = executor;
    22. }
    23. public void setRegObservers(List observers) {
    24. regObservers.addAll(observers);
    25. }
    26. public Long register(String telephone, String password) {
    27. //省略输入参数的校验代码
    28. //省略userService.register()异常的try-catch代码
    29. long userId = userService.register(telephone, password);
    30. for (RegObserver observer : regObservers) {
    31. executor.execute(new Runnable() {
    32. @Override
    33. public void run() {
    34. observer.handleRegSuccess(userId);
    35. }
    36. });
    37. }
    38. return userId;
    39. }
    40. }

           对于第一种实现方式,频繁地创建和销毁线程比较耗时,并且并发线程数无法控制,创建过多的线程会导致堆栈溢出。第二种实现方式,尽管利用了线程池解决了第一种实现方式的问题,但线程池、异步执行逻辑都耦合在了register()函数中,增加了这部分业务代码的维护成本。

    4-EventBus

           Google Guava EventBus就是一个比较著名的EventBus框架,它不仅仅支持异步非阻塞模式,同时也支持同步阻塞模式。

    1. public class UserController {
    2. private UserService userService; // 依赖注入
    3. private EventBus eventBus;
    4. private static final int DEFAULT_EVENTBUS_THREAD_POOL_SIZE = 20;
    5. public UserController() {
    6. //eventBus = new EventBus(); // 同步阻塞模式
    7. eventBus = new AsyncEventBus(Executors.newFixedThreadPool(DEFAULT_EVENTBUS_THREAD_POOL_SIZE)); // 异步非阻塞模式
    8. }
    9. public void setRegObservers(List observers) {
    10. for (Object observer : observers) {
    11. eventBus.register(observer);
    12. }
    13. }
    14. public Long register(String telephone, String password) {
    15. //省略输入参数的校验代码
    16. //省略userService.register()异常的try-catch代码
    17. long userId = userService.register(telephone, password);
    18. eventBus.post(userId);
    19. return userId;
    20. }
    21. }
    22. public class RegPromotionObserver {
    23. private PromotionService promotionService; // 依赖注入
    24. @Subscribe
    25. public void handleRegSuccess(long userId) {
    26. promotionService.issueNewUserExperienceCash(userId);
    27. }
    28. }
    29. public class RegNotificationObserver {
    30. private NotificationService notificationService;
    31. @Subscribe
    32. public void handleRegSuccess(long userId) {
    33. notificationService.sendInboxMessage(userId, "...");
    34. }
    35. }
    36.       利用EventBus框架实现的观察者模式,跟从零开始编写的观察者模式相比,从大的流程上来说,实现思路大致一样,都需要定义Observer,并且通过register()函数注册Observer,也都需要通过调用某个函数(比如,EventBus中的post()函数)来给Observer发送消息(在EventBus中消息被称作事件event)。

             基于EventBus,我们不需要定义Observer接口,任意类型的对象都可以注册到EventBus中,通过@Subscribe注解来标明类中哪个函数可以接收被观察者发送的消息。

    37. 相关阅读:
      解析Facebook对当代文化传播的影响力
      Wpf 使用 Prism 实战开发Day21
      利用洛必达法则求极限
      vscode设置pycharm中的项目路径和debug方法
      MMrotate_dev 1.x训练自己的数据集
      【MLOps】优化超参数
      everything常用搜索命令
      java计算机毕业设计交通非现场执法系统源码+系统+mysql数据库+lw文档+部署
      Java中三种方法重复使用同一输入流
      为Yolov7环境安装Cuba匹配的Pytorch
    38. 原文地址:https://blog.csdn.net/ycmy2017/article/details/134441610