后端思维篇:如何抽一个观察者模板(一)

HoverInTheSky
发布于 2022-6-27 17:31
浏览
0收藏

前言


大家好,我是捡田螺的小男孩

 

本文是后端思维专栏的第五篇哈,我的整个后端思维专栏都是跟日常工作相关的哈。今天跟大家聊聊什么是观察者模式,如何应用到工作实践中,以及如何抽取一个观察者模板。

 

1.观察者模式定义
2.观察者模式的应用场景
3.如何实现一个简单的观察者模式
4.工作中,如何使用观察者模式的
5.Spring观察者模式原理
6.基于spring观察者模式,抽取一个通用模板
7.唠叨几句,总结一下


1. 观察者模式定义


观察者模式,也可以称之为发布订阅模式,它在GoF 的《设计模式》中,是这么定义的:

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically。


翻译过来就是:观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被完成业务的更新

 

观察者模式属于行为模式,一个对象(被观察者)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。它的主要成员就是观察者和被观察者

 

 •被观察者(Observerable):目标对象,状态发生变化时,将通知所有的观察者。
 •观察者(observer):接受被观察者的状态变化通知,执行预先定义的业务。


2. 观察者模式的应用场景


哪些场景我们可以考虑使用观察者模式呢?

 

我们日常生活中,其实就有观察者模式类似的例子。比如,我们订阅了报社一年的报纸。每天报社印刷好报纸,就送到我们手中。我们就是观察者,报社就是被观察者。


而日常开发中,观察者模式的使用场景主要表现在:完成一件事情后,通知处理某个逻辑。如,登陆成功发个IM消息,支付成功发个邮件消息或者发个抽奖消息,用户评论成功给他发个积分等等。

 

举个详细点的例子吧,登陆注册应该是最常见的业务场景了,我们就拿注册来说事,大家经常会遇到类似的场景,就是用户注册成功后,我们给用户发一条IM消息,又或者发个邮件等等,因此经常有如下的代码:

void register(User user){
  insertRegisterUser(user);
  sendIMMessage();
  sendEmail();
}

这块代码会有什么问题呢?如果产品又加需求:现在注册成功的用户,再给用户发一条短信通知。于是你又得改register方法的代码了。。。这是不是违反了开闭原则啦。

void register(User user){
  insertRegisterUser(user);
  sendIMMessage();
  sendMobileMessage();
  sendEmail();
}

并且,如果调发短信的接口失败了,是不是又影响到用户注册了?!这时候,是不是得加个异步方法,异步发通知消息才好??其实这种场景,我们可以使用异步非阻塞的观察者模式优化的。

 

3. 如何实现一个简单的观察者模式


我们先来看下,简单的观察者模式如何实现。可以这么定义

 

 •一个主题接口Subject(声明添加、删除、通知观察者方法)
 •一个Observer观察者接口
 •一个创建主题的类ObserverableImpl(即被观察者),实现了Subject接口
 •各个观察者的差异化实现


为了通俗易懂,可以这样理解观察者模式:就是被观察者(ObserverableImpl)做了一件事情,或者说发布了一个主题(Subject),然后这件事情通知到各个相关的不同的人(不同的观察者,Observer的差异化实现者)。后端思维篇:如何抽一个观察者模板(一)-鸿蒙开发者社区一个主题接口

public interface Subject {

    /**
     * 添加观察者
     * @param observer
     */
    void addServer(Observer observer);

    /**
     * 移除观察者
     * @param observer
     */
    void removeServer(Observer observer);

    /**
     * 通知观察者
     * @param msg
     */
    void notifyAllObservers(String msg);

}

一个Observer接口

/**
 * 观察者
 *
 */
public interface Observer {
    /**
     * 更新消息
     * @param msg
     */
    void update(String msg);
}

一个创建主题的类ObserverableImpl(即被观察者),同时有观察者列表的属性(其实就是说观察者要事先注册到被观察者)

public class ObserverableImpl implements Subject {

    /**
     * 存储被观察者
     */
    private List<Observer> observers = new ArrayList<Observer>();

    @Override
    public void addServer(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeServer(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyAllObservers(String msg) {
        for (Observer observer : observers) {
            observer.update(msg);
        }
    }
}

 

观察者的差异化实现,以及使用

public class ObserverOneImpl implements Observer {
    @Override
    public void update(String msg) {
        System.out.println("ObserverOne is notified,"+msg);
    }
}

public class ObserverTwoImpl implements Observer {

    @Override
    public void update(String msg) {
        System.out.println("ObserverTwo is notified,"+msg);
    }
}

public class ObserverDemoTest {
    public static void main(String[] args) {
        Subject subject = new ObserverableImpl();
        //添加观察者
        subject.addObserver(new ObserverOneImpl());
        subject.addObserver(new ObserverTwoImpl());
        //通知
        subject.notifyAllObservers("关注公众号:捡田螺的小男孩");
    }
}
//输出
ObserverOne is notified,关注公众号:捡田螺的小男孩
ObserverTwo is notified,关注公众号:捡田螺的小男孩

就这样,我们实现了观察者模式啦,是不是很简单?不过上面的代码,只能算是观察者模式的模板代码,只能反映大体的设计思路。接下来,我们看下在工作中,是如何使用观察者模式的。

 

4. 工作中,如何使用观察者模式的


观察者模式的实现有两种方式,同步阻塞方式和异步非阻塞方式。第3小节就是一个同步阻塞方式的观察者模式。我们来看下,日常工作的例子:用户注册成功发消息的例子,如何实现。本小节分同步阻塞、异步阻塞、spring观察者模式三个方向探讨。

 

 •同步阻塞方式的观察模式
 •异步非阻塞方式的观察者模式
 •spring观察者模式应用


4.1 同步阻塞方式的观察模式


我们可以把用户注册,当做被观察者实现的逻辑,然后发消息就是观察者的实现逻辑

 

假设有两个观察者,分  别是发QQ消息和手机消息,于是有以下代码:

public interface RegisterObserver {
    void sendMsg(String msg);
}
@Service
public class ObserverMobileImpl implements RegisterObserver {
    @Override
    public void sendMsg(String msg) {
        System.out.println("发送手机短信消息"+msg);
    }
}
@Service
public class ObserverQQImpl implements RegisterObserver {
    @Override
    public void sendMsg(String msg) {
        System.out.println("发送QQ消息"+msg);
    }
}

直接可以通过springApplicationContextAware,初始化观察者列表,然后用户注册成功,通知观察者即可。代码如下:

@RestController
public class UserController implements ApplicationContextAware{

     @Autowired
     private UserService userService;

     //观察者列表
     private Collection<RegisterObserver> regObservers;

     @RequestMapping("register")
     public String register(UserParam userParam) {
          //注册成功过(类似于被观察者,做了某件事)
          userService.addUser(userParam);
          //然后就开始通知各个观察者。
          for(RegisterObserver temp:regObservers){
               temp.sendMsg("注冊成功");
          }
          return "SUCCESS";
     }

     //利用spring的ApplicationContextAware,初始化所有观察者
     @Override
     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
          regObservers = new ArrayList<>(applicationContext.getBeansOfType(RegisterObserver.class).values());
     }
}

可以发现,观察者模式,就是将不同的行为代码解耦,也就是说将观察者和被观察者代码解耦。但是这里大家会发现,这是同步阻塞式的观察者模式,是有缺点的,比如发QQ消息异常,就会影响用户注册,或者发消息因为某些原因耗时,就影响了用户注册,所以可以考虑异步非阻塞的观察者模式。

 

4.2 异步非阻塞方式的观察者模式


如何实现异步非阻塞,最简单就是另开个线程嘛,即新开个线程或者线程池异步跑观察者通知。代码如下:

@RestController
public class UserController implements ApplicationContextAware{

     @Autowired
     private UserService userService;

     private Collection<RegisterObserver> regObservers;

     private Executor executor = Executors.newFixedThreadPool(10);

     @RequestMapping("register")
     public String register(UserParam userParam) {
          userService.addUser(userParam);
          //异步通知每个观察者
          for (RegisterObserver temp : regObservers) {
               executor.execute(() -> {
                    temp.sendMsg("注冊成功");
               });
          }

          return "SUCCESS";
     }

     @Override
     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
          regObservers = new ArrayList<>(applicationContext.getBeansOfType(RegisterObserver.class).values());
     }
}

线程池实现的异步非阻塞方式,还是可以的,但是异步执行逻辑都耦合在了register()函数中,不是很优雅,也增加了这部分业务代码的维护成本。一般日常工作中,我们会用spring那一套观察者模式等

 

4.3 spring观察者模式应用


spring的观察者模式使用也是比较简单的,就是先定义个事件,继承于ApplicationEvent

public class MessageEvent extends ApplicationEvent {

    public MessageEvent(Object source) {
        super(source);
    }
}

然后定义一个事件监听器MessageListener,类似于观察者,它实现ApplicationListener接口

@Component
public class MessageListener implements ApplicationListener<MessageEvent> {
    @Override
    public void onApplicationEvent(MessageEvent messageEvent) {
       System.out.println("用户注册成功,执行监听事件"+messageEvent.getSource());
    }
}

用户注册成功后,applicationEventPublisher类似于被观察者)发布事件即可,代码如下:

@RestController
public class UserController implements ApplicationContextAware{

     @Autowired
     private UserService userService;
     
     @Autowired
     private ApplicationEventPublisher applicationEventPublisher;

    @RequestMapping("springListenRegister")
    public String springListenRegister(UserParam userParam) {
        System.out.println("开始注册");
        userService.addUser(userParam);
        //用户注册成功,发布事件
        applicationEventPublisher.publishEvent(new MessageEvent("666"));
        return "SUCCESS";
    }

运行结果:

开始注册
用户注册成功,执行监听事件666

这个也是同步阻塞的方式实现的,等下下个小节先介绍完spring观察者模式的原理,田螺哥再来教大家如何抽取一个通用的异步非阻塞观察者模式哈。

标签
已于2022-6-27 17:31:28修改
收藏
回复
举报
回复
    相关推荐