zoukankan      html  css  js  c++  java
  • Java设计模式之(十二)——观察者模式

    1、什么是观察者模式?

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

    观察者模式(Observer Design Pattern):在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会得到通知并自动更新。

    说人话:也叫发布订阅模式,能够很好的解耦一个对象改变,自动改变另一个对象这种情况。

    2、观察者模式定义

    image-20210919214718188

    ①、Subject 被观察者

    定义被观察者必须实现的职责, 它必须能够动态地增加、 取消观察者。 它一般是抽象类或者是实现类, 仅仅完成作为被观察者必须实现的职责: 管理观察者并通知观察者。

    ②、Observer观察者

    观察者接收到消息后, 即进行update(更新方法) 操作, 对接收到的信息进行处理。

    ③、ConcreteSubject具体的被观察者

    定义被观察者自己的业务逻辑, 同时定义对哪些事件进行通知。

    ④、ConcreteObserver具体的观察者

    每个观察在接收到消息后的处理反应是不同, 各个观察者有自己的处理逻辑。

    3、观察者模式通用代码

    /**
     * 观察者
     */
    public interface Observer {
        // 更新方法
        void update();
    }
    
    /**
     * 具体观察者
     */
    public class ConcreteObserver implements Observer{
        @Override
        public void update() {
            System.out.println("接受到信息,并进行处理");
        }
    }
    
    /**
     * 被观察者
     */
    public abstract class Subject {
        // 定义一个被观察者数组
        private List<Observer> obsList = new ArrayList<>();
    
        // 增加一个观察者
        public void addObserver(Observer observer){
            obsList.add(observer);
        }
    
        // 删除一个观察者
        public void delObserver(Observer observer){
            obsList.remove(observer);
        }
    
        // 通知所有观察者
        public void notifyObservers(){
            for (Observer observer : obsList){
                observer.update();
            }
        }
    }
    
    /**
     * 具体被观察者
     */
    public class ConcreteSubject extends Subject{
        // 具体的业务
        public void doSomething(){
            super.notifyObservers();
        }
    }
    
    public class ObserverClient {
    
        public static void main(String[] args) {
            // 创建一个被观察者
            ConcreteSubject subject = new ConcreteSubject();
            // 定义一个观察者
            Observer observer = new ConcreteObserver();
            // 观察者观察被观察者
            subject.addObserver(observer);
            subject.doSomething();
        }
    }
    

    4、JDK 实现

    在 JDK 的 java.util 包下,已经为我们提供了观察者模式的抽象实现,感兴趣的可以看看,内部逻辑其实和我们上面介绍的差不多。

    观察者 java.util.Observer

    image-20210920164737899

    被观察者 java.util.Observable

    image-20210920165032781

    image-20210920165205123

    5、实例

    用户进行注册,注册完成之后,会发一封欢迎邮件。

    5.1 普通实现

    image-20210920220940735
    public class UserController {
    
        public void register(String userName, String passWord){
            // 1、根据用户名密码保存在数据库
            Long userId = saveUser(userName, passWord);
            // 2、如果上一步有结果则发送一封欢迎邮件
            if(userId != null){
                Mail.sendEmail(userId);
            }
        }
    
    
        public Long saveUser(String userName, String passWord){
            return 1L;
        }
    }
    

    上面的注册接口实现了两件事,注册和发送邮件,很明显违反了单一职责原则,但假设这个注册需求是不是经常变动的,这样写也没有什么问题,但是假如需求变动,比如不仅要发送邮件,还得发送短信,那还这样写,那register接口会变得很复杂。

    那应该如何简化呢?没错,就是观察者模式。

    image-20210920221425379

    5.2 观察者模式实现

    我们直接套用 JDK 的实现。

    import java.util.Observable;
    
    /**
     * 用户登录——被观察者
     */
    public class UserControllerObservable extends Observable {
    
        public void register(String userName, String passWord){
            // 1、根据用户名密码保存在数据库
            Long userId = saveUser(userName, passWord);
            // 2、如果上一步有结果则通知所有观察者
            if(userId != null){
                super.setChanged();
                super.notifyObservers(userName);
            }
        }
    
        public Long saveUser(String userName, String passWord){
            return 1L;
        }
    
    }
    
    import java.util.Observable;
    import java.util.Observer;
    
    /**
     * 发送邮件——观察者
     */
    public class MailObserver implements Observer {
    
        @Override
        public void update(Observable o, Object arg) {
            System.out.println("发送邮件:" + arg + "欢迎你");
        }
    }
    
    /**
     * 发送手机短信——观察者
     */
    public class SMSObserver implements Observer {
    
        @Override
        public void update(Observable o, Object arg) {
            System.out.println("发送短信:" + arg + "欢迎你");
        }
    }
    

    测试:

    public class UserClient {
        public static void main(String[] args) {
            UserControllerObservable observable = new UserControllerObservable();
            observable.addObserver(new MailObserver());
            observable.addObserver(new SMSObserver());
            observable.register("张三","123");
        }
    }
    

    image-20210920224858256

    通过观察者模式改写后,后面用户注册,就算在增加别的操作,我们也只需要增加一个观察者即可,而注册接口 register 不会有任何改动。

    5.3 异步模式优化

    在回到前面那张图:

    image-20210920221425379

    注册之后进行的两步操作:发送邮件和发送短信,上面我们通过观察者模式改写之后,虽然流程很清晰,但是我们发现是顺序执行的,但其实这两步操作没有先后顺序,于是,我们可以改成异步模式,增加执行效率。

    /**
     * 发送邮件——观察者
     */
    public class MailObserver implements Observer {
        
        private Executor executor = Executors.newFixedThreadPool(2);
    
        @Override
        public void update(Observable o, Object arg) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("发送邮件:" + arg + "欢迎你");
                }
            });
    
        }
    }
    

    5、EventBus

    翻译为“事件总线”,它提供了实现观察者模式的骨架代码。我们可以基于此框架,非常容易地在自己的业务场景中实现观察者模式,不需要从零开始开发。其中,Google Guava EventBus 就是一个比较著名的 EventBus 框架,它不仅仅支持异步非阻塞模式,同时也支持同步阻塞模式。

    PS:Google Guava 是一个特别好用的工具包,里面的代码也都实现的比较优雅,大家感兴趣的可以研究研究源码。

    https://github.com/google/guava

    下面我们以上面的例子来说明如何使用 EventBus:

    ①、导如 Guava 包

    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>30.1.1-jre</version>
    </dependency>
    

    ②、具体代码如下:

    import com.google.common.eventbus.AsyncEventBus;
    import com.google.common.eventbus.EventBus;
    
    import java.util.List;
    import java.util.concurrent.Executors;
    
    public class UserController {
        private EventBus eventBus;
    
        public UserController(){
            eventBus = new AsyncEventBus(Executors.newFixedThreadPool(2));
        }
    
        /**
         * 注意:泛型参数是 Object,而不是接口 Observer
         * @param observerList
         */
        public void setObserverList(List<Object> observerList){
            for(Object observer : observerList){
                eventBus.register(observer);
            }
        }
    
        public void register(String userName, String passWord){
            // 1、根据用户名密码保存在数据库
            Long userId = saveUser(userName, passWord);
            // 2、如果上一步有结果则通知所有观察者
            if(userId != null){
                eventBus.post(userName);
            }
        }
    
    
        public Long saveUser(String userName, String passWord){
            return 1L;
        }
    }
    
    import com.google.common.eventbus.Subscribe;
    
    /**
     * 发送邮件——观察者
     */
    public class MailObserver{
    
        @Subscribe
        public void sendMail(String userName) {
            System.out.println("发送邮件:" + userName + "欢迎你");
        }
    }
    
    import com.google.common.eventbus.Subscribe;
    
    /**
     * 发送手机短信——观察者
     */
    public class SMSObserver{
    
        @Subscribe
        public void sendSMS(String userName) {
            System.out.println("发送短信:" + userName + "欢迎你");
        }
    }
    

    测试:

    public class EventBusClient {
        public static void main(String[] args) {
            UserController userController = new UserController();
            List<Object> observerList = new ArrayList<>();
            observerList.add(new MailObserver());
            observerList.add(new SMSObserver());
            userController.setObserverList(observerList);
            userController.register("张三","123");
        }
    }
    

    利用 EventBus 框架实现的观察者模式,跟从零开始编写的观察者模式相比,从大的流程上来说,实现思路大致一样,都需要定义 Observer,并且通过 register() 函数注册 Observer,也都需要通过调用某个函数(比如,EventBus 中的 post() 函数)来给 Observer 发送消息(在 EventBus 中消息被称作事件 event)。但在实现细节方面,它们又有些区别。基于 EventBus,我们不需要定义 Observer 接口,任意类型的对象都可以注册到 EventBus 中,通过 @Subscribe 注解来标明类中哪个函数可以接收被观察者发送的消息。

    6、观察者模式优点

    ①、观察者和被观察者之间是抽象耦合

    不管是增加观察者还是被观察者都非常容易扩展,在系统扩展方面会得心应手。

    ②、建立一套触发机制

    被观察者变化引起观察者自动变化。但是需要注意的是,一个被观察者,多个观察者,Java的消息通知默认是顺序执行的,如果一个观察者卡住,会导致整个流程卡住,这就是同步阻塞。

    所以实际开发中没有先后顺序的考虑使用异步,异步非阻塞除了能够实现代码解耦,还能充分利用硬件资源,提高代码的执行效率。

    另外还有进程间的观察者模式,通常基于消息队列来实现,用于实现不同进程间的观察者和被观察者之间的交互。

    7、观察者模式应用场景

    ①、关联行为场景。

    ②、事件多级触发场景。

    ③、跨系统的消息交换场景, 如消息队列的处理机制。

    作者:IT可乐

    资源:微信搜【IT可乐】关注我,回复 【电子书】有我特别筛选的免费电子书。
    本文版权归作者所有,欢迎转载,但未经作者同意不能转载,否则保留追究法律责任的权利。
  • 相关阅读:
    struts2中<s:select>标签的使用
    正则表达式(括号)、[中括号]、{大括号}的区别小结
    解读邮箱正则表达式:^w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*$
    Struts2国际化-getText()方法
    Eclipse的Tomcat热部署,免重启的方法
    hdu 5056Boring count
    纸板上的虚拟现实和代码中的Cardboard
    OC-Protocol实现业务代理
    UVA 11237
    mysql相关日志汇总
  • 原文地址:https://www.cnblogs.com/ysocean/p/15627036.html
Copyright © 2011-2022 走看看