「设计模式」🔭观察者模式(Observer)

608 阅读8分钟

🎉创建型模式关注对象的创建过程;结构型模式关注对象与类的组织结构;而从本文开始往后的行为型模式则关注对象之间的交互!

没错,创建型模式结构型模式部分都已完结啦!接下来持续探索 GoF 23 种设计模式中所包含的 11 种行为型模式,学习如何在软件系统的设计与开发中合理使用这些模式。

⭐行为型模式关注系统中对象之间的相互交互,研究系统在运行时对象之间的相互通信与协作。行为型模式又可细分为类行为型模式对象行为型模式两种:

  • 类行为型模式使用继承关系在类之间分配行为。
  • 对象行为型模式则使用对象的聚合关联关系来分配行为。

根据 “合成复用原则” 的指导,在系统中尽量使用关联关系来替代继承关系,因此大部分行为型模式都属于对象行为型模式。

以上对行为型模式做了个简要的介绍,话不多说,我们开启今天的学习之旅吧!

模式动机

在现实生活中,假设你订阅了一份杂志或报纸,那你就不再需要去报摊查询新出版的刊物了,出版社(发布者)会在刊物出版后直接将最新一期寄送到你(订阅者)的邮箱中,从而使得你能及时阅读自己所喜爱的刊物。

出版社负责维护订阅者列表,了解订阅者对哪些刊物感兴趣,当订阅者希望出版社停止寄送新一期的杂志时,他们可以随时取消订阅以从该列表退出。

⭐以上就是观察者模式(发布—订阅模式)的一个现实模型,拥有一些值得关注的状态的对象通常被称为目标 Subject,由于它要将自身的状态改变通知给其他对象,我们也将其称为发布者 Publisher,所有希望关注发布者状态变化的其他对象被称为观察者 Observer,或者订阅者 Subscriber

🤔生活中的观察者模式可不止于此,生活中许多对象也都同观察者模式中的对象一样:并不是独立存在的,其中一个对象的行为发生改变可能会导致一个或者多个其他对象的行为也发生改变。比如:

  • 股票市场中的涨跌会影响股民的情绪
  • 交叉路口的红绿灯指引着行人的行为
  • 气象局的天气预报一定程度上决定了人们的出行
  • 微信公众号的订阅用户能及时收到发布者推送的文章
  • 人民币汇率的变化时刻影响着进出口公司的销售利润率

😆在软件系统中也存在诸多实例,比如:

  • MVC 模式中的模型与视图的关系
  • Java Swing/AWT 中的控件响应方式

诸如此类都是使用「观察者模式」实现的...

😐emmm... 单纯举例有点不痛不痒,我们尝试来理解下其中的运行机制吧,这会对你读懂下文的类图起到很大的帮助。

观察者模式会为发布者构建 添加订阅取消订阅 的机制,为对此感兴趣的对象提供订阅/取消订阅的途径。

订阅机制

为了让所有的订阅者能跟踪同一个发布者触发的事件,我们必须让所有的订阅者实现同样的接口,发布者仅通过该接口与订阅者交互。接口中必须声明通知方法及其参数,这样发布者在发出通知时还能传递一些上下文数据

发布者调用订阅者对象中的特定接口来通知订阅者

😊很简单?对吧!如果一知半解,请耐心往下看,等看到代码层面时你一定会恍然大悟的。

定义

观察者模式又称发布—订阅者模式(Publish/Subscribe)、模型—视图模式源—监听器模式,它是一种对象行为型模式

观察者模式定义了对象间一种一对多的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将做出相应的反应。其中,发生改变的对象称为观察目标 Subject,而被通知的对象称为观察者 Observer,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。

UML 类图

模式结构

观察者模式包含如下角色:

  • Subject:抽象目标类是被观察的一方,它定义了一个观察者集合,允许新观察者加入和当前观察者离开列表,同时声明了通知接口。
  • ConcreteSubject:具体目标类继承自抽象目标类,当新事件发生时,它会遍历观察者列表并逐个调用每个观察者的响应方法。
  • Observer:抽象观察者声明了接收到目标通知后的响应接口,通常定义为 Interface
  • ConcreteObserver:具体观察者具体实现了响应接口,以便在得到目标更改通知时更新自身状态以及作出相应的反应。

更多实例

如上的 subjectState 其实可以抽取成一个单独的事件类,用于传递通知消息,从而让观察者据此做出反应。

以下演示 “不抽取事件类” 与 “抽取事件类” 的 2 个实例。

【1】利用观察者模式分析 ”人民币汇率“ 的升值或贬值对进口公司进口产品成本或出口公司的出口产品收入以及公司利润率的影响。

【2】利用观察者模式比拟铃声对老师与学生行为的影响。

示例代码

Observer.java

public interface Observer {
    void response(String subjectState);
}

ConcreteObserver1.java

public class ConcreteObserver1 implements Observer {
    @Override
    public void response(String subjectState) {
        // TODO: react by subject's notifying —— subjectState
        System.out.println("observer1 get the subjectState '" + subjectState + "', get Happy..");
    }
}

ConcreteObserver2.java

public class ConcreteObserver2 implements Observer {
    @Override
    public void response(String subjectState) {
        // TODO: react by subject's notifying —— subjectState
        System.out.println("observer2 get the subjectState '" + subjectState + "', get Angry..");
    }
}

Subject.java

public abstract class Subject {
    protected List<Observer> observers = new ArrayList<>();

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

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

    public abstract void publish();
}

ConcreteSubject.java

public class ConcreteSubject extends Subject {
    private String subjectState;

    public ConcreteSubject(String subjectState) {
        this.subjectState = subjectState;
    }

    @Override
    public void publish() {
        System.out.println("Event changes!");
        for (Object obs : observers) {
            ((Observer) obs).response(subjectState);
        }
    }
}

Client.java

public class Client {
    public static void main(String[] args) {
        Subject subject = new ConcreteSubject("Off duty");
        Observer observer1 = new ConcreteObserver1();
        Observer observer2 = new ConcreteObserver2();

        subject.add(observer1);
        subject.add(observer2);
        subject.publish();

        System.out.println("-----------------------------------------------------");

        subject.remove(observer2);
        subject.publish();
    }
}

运行结果:

优缺点

✔观察者模式可以实现表示层与业务逻辑层的分离,并定义了稳定的消息更新传递机制。

✔观察者与观察目标之间仅存在一层抽象的耦合,且能在需要时被其他应用复用。

✔观察者模式符合 “开闭原则”,无需修改代码就能引入观察者和观察目标。

❌如果在观察者和观察目标之间存在循环依赖,可能会导致系统崩溃。

❌观察目标对观察者的通知顺序是随机的,且观察者无法知晓目标对象是如何发生变化的,仅仅知道发生了变化。

适用场景

在以下情况推荐使用观察者模式:

(1)一个抽象模型有两个方面,其中一个方面依赖于另一个方面,需要将这些方面封装在独立的对象中使得它们可以各自独立地改变和复用。

(2)当一个对象状态的改变需要改变其他对象,或实际对象是事先未知的或动态变化的时候。

(3)需要在系统中创建一个触发链,A 对象的行为会影响 B,B 对象的行为会影响 C ...,可以使用观察者模式创建该种链式触发机制。

「观察者模式」落地

JDK 对观察者模式的原生支持

😆Java 语言不能没有观察者模式,就像西方不能没有耶路撒冷 (doge) !

在 JDK 的 java.util 包中,提供了 Observable 类以及 Observer 接口,前者为观察目标而后者则是观察者

Java 语言中的观察者模式,其中 ConcreteObservable & ConcreteObserver 分别是观察目标与观察者的具体子类,但并非 jdk 提供的类。

Observer 接口

Observer 接口充当观察者,只定义了一个方法 update(),当观察目标的状态发生变化时被调用。

Observable

Observable 类充当观察目标,相比上文所写的代码多了并发空指针方面的考量。

运用一下

推送通知主体 WeChatNotice
public class WeChatNotice {
    // 发布者
    private String publisher;
    // 文章
    private String post;

    public WeChatNotice(String publisher, String post) {
        this.publisher = publisher;
        this.post = post;
    }

    public String getPublisher() {
        return publisher;
    }

    public String getPost() {
        return post;
    }
}
发布者 WeChatAccount
public class WeChatAccount extends Observable {
    private String publisher;

    public WeChatAccount(String publisher) {
        this.publisher = publisher;
    }

    // 发布新文章
    public void publishArticle(String post) {
        WeChatNotice notice = new WeChatNotice(this.publisher, post);
        System.out.println(String.format("微信公众号「%s」推送了一篇新文章: [%s]!", publisher, post));
        setChanged();
        notifyObservers(notice);
    }
}
订阅者 WeChatSubscriber
public class WeChatSubscriber implements Observer {
    private String subscriber;

    public WeChatSubscriber(String subscriber) {
        this.subscriber = subscriber;
    }

    @Override
    public void update(Observable o, Object arg) {
        // WeChatAccount account = (WeChatAccount) o;
        WeChatNotice notice = (WeChatNotice) arg;

        System.out.println(String.format("[%s] 收到微信公众号「%s」推送的新文章: %s", subscriber, notice.getPublisher(), notice.getPost()));
    }
}
测试代码 WeChatTest
public class WeChatTest {
    public static void main(String[] args) {
        WeChatAccount publisher = new WeChatAccount("掘了");
        WeChatSubscriber subscriber1 = new WeChatSubscriber("法外狂徒-张三");
        WeChatSubscriber subscriber2 = new WeChatSubscriber("饭醉首脑-李四");
        publisher.addObserver(subscriber1);
        publisher.addObserver(subscriber2);
        publisher.publishArticle("刑法知多少");

        publisher.deleteObserver(subscriber2);
        publisher.publishArticle("观察者模式");
    }
}

MVC 模式

MVC 模式是一种架构模式,它包含三个角色:模型(Model)、视图(View)和控制器(Controller)。

观察者模式可以用来实现 MVC 模式,观察者模式中的观察目标就是 MVC 模式中的模型(Model),而观察者就是 MVC 中的视图(View),控制器(Controller)充当两者之间的中介者(Mediator)。当模型层的数据发生改变时,视图层将自动改变其显示内容。

实际上,MVC 模式蕴含了观察者模式、中介者模式等设计模式,大家如果感兴趣可以查阅 Java SE Application Design With MVC 技术文档对 MVC 模式进行深入的了解和学习。

Spring 观察者模式下的事件机制

Spring 基于观察者模式,实现了自身的事件机制,主要由三部分组成:

  • 事件 ApplicationEvent:通过继承该类,实现自定义事件,用于传递数据。其中,它的 source 属性可以获取到事件源,timestamp 属性可以获取到发生时间。
  • 事件发布者 ApplicationEventPublisher:通过其中的 publishEvent() 可以进行事件的发布。
  • 事件监听者 ApplicationListener:通过实现该接口,进行指定类型的事件监听。

友情提醒:JDK 中内置了事件机制的实现,考虑到通用性,Spring 的事件机制基于它之上进行拓展。因此,ApplicationEvent 继承自 java.util.EventObjectApplicationListener 继承自 java.util.EventListener

🚀如果想要深入学习 Spring 中的观察者模式,不妨看看这篇文章:别再面向 for 循环编程了,Spring 自带的观察者模式就很香!

最后

👆上一篇:「设计模式」🧩享元模式(Flyweight)

👇下一篇:持续更文中,敬请期待...

❤️ 好的代码无需解释,关注「手撕设计模式」专栏,跟我一起学习设计模式,你的代码也能像诗一样优雅!

❤️ / END / 如果本文对你有帮助,点个「赞」支持下吧,你的支持就是我最大的动力!