设计模式

设计模式 | 观察者模式及典型应用

2019-01-05  本文已影响111人  小旋锋的简书

本文主要内容:

观察者模式

观察者模式是设计模式中的 "超级模式",其应用随处可见,我们以微信公众号为例。

微信公众号有服务号、订阅号和企业号之分。以我的公众号为例,我的公众号类型是订阅号,名称是 "小旋锋",专注于大数据,Java后端类技术分享。目前主要是分享学习笔记为主,尽量做到 "原创"、"高质量"、"成体系"。每当我发布一篇博文推送,订阅的用户都能够在我发布推送之后及时接收到推送,即可方便地在手机端进行阅读。

微信公众号.发布/订阅

观察者模式(Observer Pattern):定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式是一种对象行为型模式。

观察者模式的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

观察者模式包含观察目标和观察者两类对象,一个目标可以有任意数目的与之相依赖的观察者,一旦观察目标的状态发生改变,所有的观察者都将得到通知。

角色

Subject(目标):目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notify()。目标类可以是接口,也可以是抽象类或具体类。

ConcreteSubject(具体目标):具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有的话)。如果无须扩展目标类,则具体目标类可以省略。

Observer(观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法update(),因此又称为抽象观察者。

ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者Observer中定义的update()方法。通常在实现时,可以调用具体目标类的attach()方法将自己添加到目标类的集合中或通过detach()方法将自己从目标类的集合中删除。

示例

首先需要一个订阅者接口(观察者),该接口有一个 receive 方法,用于接收公众号推送通知

public interface Subscriber {
    int receive(String publisher, String articleName);
}

然后是一个微信客户端(具体观察者),实现了 receive 方法

public class WeChatClient implements Subscriber {
    private String username;

    public WeChatClient(String username) {
        this.username = username;
    }

    @Override
    public int receive(String publisher, String articleName) {
        // 接收到推送时的操作
        System.out.println(String.format("用户<%s> 接收到 <%s>微信公众号 的推送,文章标题为 <%s>", username, publisher, articleName));
        return 0;
    }
}

发布者类(目标,被观察对象),该类维护了一个订阅者列表,实现了订阅、取消订阅、通知所有订阅者等功能

public class Publisher {
    private List<Subscriber> subscribers;
    private boolean pubStatus = false;

    public Publisher() {
        subscribers = new ArrayList<Subscriber>();
    }

    protected void subscribe(Subscriber subscriber) {
        this.subscribers.add(subscriber);
    }

    protected void unsubscribe(Subscriber subscriber) {
        if (this.subscribers.contains(subscriber)) {
            this.subscribers.remove(subscriber);
        }
    }

    protected void notifySubscribers(String publisher, String articleName) {
        if (this.pubStatus == false) {
            return;
        }
        for (Subscriber subscriber : this.subscribers) {
            subscriber.receive(publisher, articleName);
        }
        this.clearPubStatus();
    }

    protected void setPubStatus() {
        this.pubStatus = true;
    }

    protected void clearPubStatus() {
        this.pubStatus = false;
    }
}

微信公众号类(具体目标),该类提供了 publishArticles 方法,用于发布推送,当文章发布完毕时调用父类的通知所有订阅者方法

public class WeChatAccounts extends Publisher {
    private String name;

    public WeChatAccounts(String name) {
        this.name = name;
    }

    public void publishArticles(String articleName, String content) {
        System.out.println(String.format("\n<%s>微信公众号 发布了一篇推送,文章名称为 <%s>,内容为 <%s> ", this.name, articleName, content));
        setPubStatus();
        notifySubscribers(this.name, articleName);
    }
}

测试

public class Test {
    public static void main(String[] args) {
        WeChatAccounts accounts = new WeChatAccounts("小旋锋");

        WeChatClient user1 = new WeChatClient("张三");
        WeChatClient user2 = new WeChatClient("李四");
        WeChatClient user3 = new WeChatClient("王五");

        accounts.subscribe(user1);
        accounts.subscribe(user2);
        accounts.subscribe(user3);

        accounts.publishArticles("设计模式 | 观察者模式及典型应用", "观察者模式的内容...");

        accounts.unsubscribe(user1);
        accounts.publishArticles("设计模式 | 单例模式及典型应用", "单例模式的内容....");
    }
}

结果如下,符合预期,当公众号发布一篇推送时,订阅该公众号的用户可及时接收到推送的通知

<小旋锋>微信公众号 发布了一篇推送,文章名称为 <设计模式 | 观察者模式及典型应用>,内容为 <观察者模式的内容...> 
用户<张三> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
用户<李四> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
用户<王五> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>

<小旋锋>微信公众号 发布了一篇推送,文章名称为 <设计模式 | 单例模式及典型应用>,内容为 <单例模式的内容....> 
用户<李四> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 单例模式及典型应用>
用户<王五> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 单例模式及典型应用>

可画出类图如下

示例.观察者模式类图

借此机会做个小推广,欢迎大家关注我的微信公众号哦 ^_^

关注【小旋锋】微信公众号

观察者模式总结

观察者模式的主要优点如下:

观察者模式的主要缺点如下:

适用场景

观察者模式的典型应用

JDK 提供的观察者接口

观察者模式在Java语言中的地位非常重要。在JDK的 java.util 包中,提供了 Observable 类以及 Observer 接口,它们构成了JDK对观察者模式的支持。

其中的 Observer 接口为观察者,只有一个 update 方法,当观察目标发生变化时被调用,其代码如下:

public interface Observer {
    void update(Observable o, Object arg);
}

Observable 类则为目标类,相比我们的示例中的 Publisher 类多了并发和NPE方面的考虑

public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs = new Vector();

    public Observable() {
    }
    // 用于注册新的观察者对象到向量中
    public synchronized void addObserver(Observer var1) {
        if (var1 == null) {
            throw new NullPointerException();
        } else {
            if (!this.obs.contains(var1)) {
                this.obs.addElement(var1);
            }

        }
    }
    // 用于删除向量中的某一个观察者对象
    public synchronized void deleteObserver(Observer var1) {
        this.obs.removeElement(var1);
    }

    public void notifyObservers() {
        this.notifyObservers((Object)null);
    }
    // 通知方法,用于在方法内部循环调用向量中每一个观察者的update()方法
    public void notifyObservers(Object var1) {
        Object[] var2;
        synchronized(this) {
            if (!this.changed) {
                return;
            }

            var2 = this.obs.toArray();
            this.clearChanged();
        }

        for(int var3 = var2.length - 1; var3 >= 0; --var3) {
            ((Observer)var2[var3]).update(this, var1);
        }

    }
    // 用于清空向量,即删除向量中所有观察者对象
    public synchronized void deleteObservers() {
        this.obs.removeAllElements();
    }
    // 该方法被调用后会设置一个boolean类型的内部标记变量changed的值为true,表示观察目标对象的状态发生了变化
    protected synchronized void setChanged() {
        this.changed = true;
    }
    // 用于将changed变量的值设为false,表示对象状态不再发生改变或者已经通知了所有的观察者对象,调用了它们的update()方法
    protected synchronized void clearChanged() {
        this.changed = false;
    }
    // 返回对象状态是否改变
    public synchronized boolean hasChanged() {
        return this.changed;
    }
    // 返回向量中观察者的数量
    public synchronized int countObservers() {
        return this.obs.size();
    }
}

我们可以使用 Observable 类以及 Observer 接口来重新实现微信公众号示例。

增加一个通知类 WechatNotice,用于推送通知的传递

@Data
@AllArgsConstructor
public class WechatNotice {
    private String publisher;
    private String articleName;
}

然后改写 WeChatClientWeChatAccounts,分别实现JDK的 Observer 接口和继承 Observable

public class WeChatClient implements Observer {
    private String username;

    public WeChatClient(String username) {
        this.username = username;
    }

    @Override
    public void update(Observable o, Object arg) {
        //WeChatAccounts weChatAccounts = (WeChatAccounts) o;
        WechatNotice notice = (WechatNotice) arg;
        System.out.println(String.format("用户<%s> 接收到 <%s>微信公众号 的推送,文章标题为 <%s>", username, notice.getPublisher(), notice.getArticleName()));
    }
}

public class WeChatAccounts extends Observable {
    private String name;

    public WeChatAccounts(String name) {
        this.name = name;
    }

    public void publishArticles(String articleName, String content) {
        System.out.println(String.format("\n<%s>微信公众号 发布了一篇推送,文章名称为 <%s>,内容为 <%s> ", this.name, articleName, content));
        setChanged();
        notifyObservers(new WechatNotice(this.name, articleName));
    }
}

测试,与示例中的测试代码的区别在于调用的方法不同

public class Test {
    public static void main(String[] args) {
        WeChatAccounts accounts = new WeChatAccounts("小旋锋");

        WeChatClient user1 = new WeChatClient("张三");
        WeChatClient user2 = new WeChatClient("李四");
        WeChatClient user3 = new WeChatClient("王五");

        accounts.addObserver(user1);
        accounts.addObserver(user2);
        accounts.addObserver(user3);

        accounts.publishArticles("设计模式 | 观察者模式及典型应用", "观察者模式的内容...");

        accounts.deleteObserver(user1);
        accounts.publishArticles("设计模式 | 单例模式及典型应用", "单例模式的内容....");
    }
}

测试结果如下,可以发现结果如示例一致

<小旋锋>微信公众号 发布了一篇推送,文章名称为 <设计模式 | 观察者模式及典型应用>,内容为 <观察者模式的内容...> 
用户<王五> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
用户<李四> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
用户<张三> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>

<小旋锋>微信公众号 发布了一篇推送,文章名称为 <设计模式 | 单例模式及典型应用>,内容为 <单例模式的内容....> 
用户<王五> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 单例模式及典型应用>
用户<李四> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 单例模式及典型应用>

Guava EventBus 中的观察者模式

Guava 中的 EventBus 封装了友好的 "生产/消费模型",通过非常简单的方式,实现了观察者模式中的监听注册,事件分发。

使用了 Guava EventBus 之后,如果需要订阅消息,不需要实现任何接口,只需在监听方法上加上 @Subscribe 注解即可,EventBus 提供了 registerunregister 方法用于注册与取消注册事件,当 EventBus 调用 post 方法时将把事件分发给注册的对象

使用 Guava 重新实现示例

@Data
@AllArgsConstructor
public class WechatNotice {
    private String publisher;
    private String articleName;
}

public class WeChatClient  {
    private String username;
    
    public WeChatClient(String username) {
        this.username = username;
    }

    @Subscribe
    public void listen(WechatNotice notice) {
        System.out.println(String.format("用户<%s> 接收到 <%s>微信公众号 的推送,文章标题为 <%s>", username, notice.getPublisher(), notice.getArticleName()));
    }
}

public class WeChatAccounts {
    private String name;
    private EventBus eventBus;

    public WeChatAccounts(String name) {
        this.name = name;
        this.eventBus = new EventBus();
    }

    public void publishArticles(String articleName, String content) {
        System.out.println(String.format("\n<%s>微信公众号 发布了一篇推送,文章名称为 <%s>,内容为 <%s> ", this.name, articleName, content));
        this.eventBus.post(new WechatNotice(this.name, articleName));
    }

    public void register(WeChatClient weChatClient) {
        this.eventBus.register(weChatClient);
    }

    public void unregister(WeChatClient weChatClient) {
        this.eventBus.unregister(weChatClient);
    }
}

测试

public class Test {
    public static void main(String[] args) {
        WeChatAccounts accounts = new WeChatAccounts("小旋锋");

        WeChatClient user1 = new WeChatClient("张三");
        WeChatClient user2 = new WeChatClient("李四");
        WeChatClient user3 = new WeChatClient("王五");

        accounts.register(user1);
        accounts.register(user2);
        accounts.register(user3);

        accounts.publishArticles("设计模式 | 观察者模式及典型应用", "观察者模式的内容...");

        accounts.unregister(user1);
        accounts.publishArticles("设计模式 | 单例模式及典型应用", "单例模式的内容....");
    }
}

不出意料,输出的内容与上面两个示例一样

<小旋锋>微信公众号 发布了一篇推送,文章名称为 <设计模式 | 观察者模式及典型应用>,内容为 <观察者模式的内容...> 
用户<张三> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
用户<李四> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
用户<王五> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>

<小旋锋>微信公众号 发布了一篇推送,文章名称为 <设计模式 | 单例模式及典型应用>,内容为 <单例模式的内容....> 
用户<李四> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 单例模式及典型应用>
用户<王五> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 单例模式及典型应用>

Guava EventBus 的更多用法可自行查看相关文档
Guava EventBus 源码分析可看这篇 http://t.cn/EZzC35B

JDK 委托事件模型DEM中的观察者模式

首先来敲一个AWT按钮监听事件的Demo

import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;


public class MouseEvents {
    private Frame frame;
    private Button button;

    MouseEvents() {
        frame = new Frame("点击按钮触发点击事件,控制台将打印日志");
        frame.setBounds(300, 200, 600, 300);
        frame.setLayout(new FlowLayout());

        button = new Button("this is a button");
        button.setFont(new Font("Default", 0, 30));
        frame.add(button);

        dealwithEvent();

        frame.setVisible(true);
    }

    //事件监听器以及处理事件
    private void dealwithEvent() {
        // 监听窗体关闭事件
        frame.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });

        button.addActionListener(new ActionListener() {
            private int eventCount = 1;

            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println(String.format("动作事件发生 %d 次", eventCount++));
            }
        });
    }

    public static void main(String[] args) {
        new MouseEvents();
    }
}

运行 main 方法桌面将弹出下面的面板和按钮

按钮监听鼠标事件

按钮的 addActionListener 添加指定的动作侦听器,以接收发自此按钮的动作事件,当用户在按钮上按下或释放鼠标时,JVM将产生一个相应的 ActionEvent 类型的事件对象,并在触发事件时将调用按钮的 fireXXX() 方法(继承自 Component),在该方法内部,将调用注册到按钮中的 ActionListener 对象的 actionPerformed() 方法(也就是我们实现的匿名事件处理类),实现对事件的处理

动作事件发生 1 次
动作事件发生 2 次
动作事件发生 3 次
动作事件发生 4 次

Spring ApplicationContext 事件机制中的观察者模式

spring的事件机制是从java的事件机制拓展而来,ApplicationContext 中事件处理是由 ApplicationEvent 类和 ApplicationListener 接口来提供的。如果一个Bean实现了 ApplicationListener 接口,并且已经发布到容器中去,每次 ApplicationContext 发布一个 ApplicationEvent 事件,这个Bean就会接到通知

使用 spring 事件机制重新实现示例

@Data
public class WechatNotice extends ApplicationEvent {
    private String publisher;
    private String articleName;

    public WechatNotice(Object source, String publisher, String articleName) {
        super(source);
        this.publisher = publisher;
        this.articleName = articleName;
    }
}

public class WeChatClient implements ApplicationListener {
    private String username;

    public WeChatClient(String username) {
        this.username = username;
    }

    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof WechatNotice) {
            WechatNotice notice = (WechatNotice) event;
            System.out.println(String.format("用户<%s> 接收到 <%s>微信公众号 的推送,文章标题为 <%s>", username, notice.getPublisher(), notice.getArticleName()));
        }
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

public class WeChatAccounts implements ApplicationContextAware {
    private ApplicationContext ctx;
    private String name;

    public WeChatAccounts(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ctx = applicationContext;
    }

    public void publishArticles(String articleName, String content) {
        System.out.println(String.format("\n<%s>微信公众号 发布了一篇推送,文章名称为 <%s>,内容为 <%s> ", this.name, articleName, content));
        ctx.publishEvent(new WechatNotice(this.name, this.name, articleName));
    }
}

在 resources 目录下创建 spring.xml 文件,填入下面的内容

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="WeChatAccounts" class="com.observer.sprintevent.WeChatAccounts" scope="prototype">
        <constructor-arg name="name" value=""></constructor-arg>
    </bean>
    <bean id="WeChatClient1" class="com.observer.sprintevent.WeChatClient">
        <constructor-arg name="username" value="张三"></constructor-arg>
    </bean>
    <bean id="WeChatClient2" class="com.observer.sprintevent.WeChatClient">
        <constructor-arg name="username" value="李四"></constructor-arg>
    </bean>
    <bean id="WeChatClient3" class="com.observer.sprintevent.WeChatClient">
        <constructor-arg name="username" value="王五"></constructor-arg>
    </bean>
</beans>

测试

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");

        WeChatAccounts accounts = (WeChatAccounts) context.getBean("WeChatAccounts");
        accounts.setName("小旋锋");
        accounts.setApplicationContext(context);

        accounts.publishArticles("设计模式 | 观察者模式及典型应用", "观察者模式的内容...");
    }
}

输出如下

<小旋锋>微信公众号 发布了一篇推送,文章名称为 <设计模式 | 观察者模式及典型应用>,内容为 <观察者模式的内容...> 
用户<张三> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
用户<李四> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>
用户<王五> 接收到 <小旋锋>微信公众号 的推送,文章标题为 <设计模式 | 观察者模式及典型应用>

在此示例中 ApplicationContext 对象的实际类型为 ClassPathXmlApplicationContext,其中的与 publishEvent 方法相关的主要代码如下:

private ApplicationEventMulticaster applicationEventMulticaster;

public void publishEvent(ApplicationEvent event) {
    this.getApplicationEventMulticaster().multicastEvent(event);
    if (this.parent != null) {
        this.parent.publishEvent(event);
    }
}

ApplicationEventMulticaster getApplicationEventMulticaster() throws IllegalStateException {
    return this.applicationEventMulticaster;
}

protected void initApplicationEventMulticaster() {
        ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
        if (beanFactory.containsLocalBean("applicationEventMulticaster")) {
            this.applicationEventMulticaster = (ApplicationEventMulticaster)beanFactory.getBean("applicationEventMulticaster", ApplicationEventMulticaster.class);
        } else {
            this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
            beanFactory.registerSingleton("applicationEventMulticaster", this.applicationEventMulticaster);
        }

    }

其中的 SimpleApplicationEventMulticaster 如下,multicastEvent 方法主要是通过遍历 ApplicationListener(注册由 AbstractApplicationEventMulticaster 实现),使用线程池框架 Executor 来并发执行 ApplicationListeneronApplicationEvent 方法,与示例本质上是一致的

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
    private Executor taskExecutor;

    public void multicastEvent(final ApplicationEvent event) {
        Iterator var2 = this.getApplicationListeners(event).iterator();

        while(var2.hasNext()) {
            final ApplicationListener listener = (ApplicationListener)var2.next();
            Executor executor = this.getTaskExecutor();
            if (executor != null) {
                executor.execute(new Runnable() {
                    public void run() {
                        listener.onApplicationEvent(event);
                    }
                });
            } else {
                listener.onApplicationEvent(event);
            }
        }

    }
}

参考:
刘伟:设计模式Java版
慕课网java设计模式精讲 Debug 方式+内存分析
用guava实现简单的事件驱动
springboot 事件机制

后记

欢迎评论、转发、分享,您的支持是我最大的动力

更多内容可访问我的个人博客:http://laijianfeng.org

关注【小旋锋】微信公众号,及时接收博文推送

关注_小旋锋_微信公众号
上一篇下一篇

猜你喜欢

热点阅读