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

举报
bug菌 发表于 2023/09/26 10:59:11 2023/09/26
【摘要】 观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,使得多个观察者对象可以同时监听某一个主题对象,主题对象状态的改变会自动通知所有观察者对象,使他们能够及时做出响应。


🏆本文收录于《聊设计模式》专栏,专门攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎持续关注&&收藏&&订阅!


大家下午好,我是bug菌,今天我们继续聊设计模式。

前言

  设计模式是软件工程领域中经典且重要的概念之一,其作用在于提供一种可复用的解决方案,帮助开发者解决面向对象设计中的常见问题。观察者模式是其中一种常见的设计模式,在实际开发中也得到了广泛的应用。

本文将从原理、实现、使用场景、优缺点、模式实现和测试用例等多方面介绍观察者模式的基本概念和实现方法。

摘要

  观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,使得多个观察者对象可以同时监听某一个主题对象,主题对象状态的改变会自动通知所有观察者对象,使他们能够及时做出响应。

观察者模式

  观察者模式的核心是在主题对象和观察者对象之间建立一种松耦合的关系,以便于主题对象状态改变时通知观察者对象做出相应的处理。

概念

  观察者模式是一种行为设计模式,也称为发布-订阅模式。在这种模式中,观察者对象订阅了主题的状态,当主题状态发生变化时,观察者会收到通知并自动更新自己的状态。观察者模式通过解耦主题和观察者的交互,使得主题和观察者可以独立地改变和扩展,从而提高应用程序的灵活性和可扩展性。它是一种常见的设计模式,在面向对象编程中有广泛的应用。

结构

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

  • 主题(Subject):也称为被观察者,它是一个具体的对象,通过attach()方法注册观察者,通过notifyObservers()方法通知观察者。
  • 观察者(Observer):也称为观察者,它定义了一个更新接口,当主题状态改变时,得到通知并进行相应的处理。
  • 具体主题(ConcreteSubject):是主题的具体实现对象,它保存着观察者对象的列表,并实现了抽象主题中的方法。
  • 具体观察者(ConcreteObserver):是观察者的具体实现对象,它存储着具体主题的引用,实现了抽象观察者中的更新接口。

如下是观察者模式的UML类图:

image.png

  具体来说,在观察者模式中,主题对象包含一个观察者列表,当主题状态发生改变时,会遍历观察者列表,调用观察者的更新接口通知他们状态已经改变。

应用场景

观察者模式通常用于以下场景:

  1. 事件处理:观察者模式可以用于事件处理系统,在系统中,事件源对象可以将事件通知所有的观察者对象,以便观察者对象可以执行相应的操作。

  2. 状态监控:观察者模式可以用于监控对象的状态变化,在状态改变时,通知所有观察者对象。

  3. MVC模式:MVC模式中,视图与数据模型是分离的,视图通常是数据模型的观察者。当数据模型发生变化时,通知所有的视图对象,更新视图。

  4. 消息系统:观察者模式可以用于实现消息系统,在系统中,消息发布者可以将消息通知所有的观察者对象,以便观察者对象可以执行相应的操作。

  5. GUI开发:观察者模式可以用于GUI开发,在GUI中,图形对象可以作为观察者,与数据模型对象建立关联,当数据模型发生变化时,通知所有的观察者对象,更新界面。

优缺点

  观察者模式是一种行为型设计模式,定义了一种一对多的依赖关系,当一个对象的状态发生改变时,它的所有依赖者都会收到通知并自动更新状态。

优点包括:

  1. 松耦合:观察者模式实现了对象之间的松耦合,被观察者和观察者之间的关系被解耦,它们都可以独立地变化。

  2. 对象的状态更新:观察者模式实现了对象的状态更新,当对象的状态发生变化时,所有依赖它的对象都会收到通知并自动更新状态。

  3. 拓展性:在观察者模式中,可以很方便地增加或删除观察者,实现拓展性。

缺点包括:

  1. 观察者模式可能导致事件的洪泛,即被观察者的状态变化导致大量的信息传递和处理,这可能降低程序的性能。

  2. 可能存在循环依赖问题:当观察者和被观察者之间存在互相依赖时,就会出现循环依赖的问题,这会导致系统的稳定性降低。

  3. 观察者模式需要考虑开发效率和运行效率两个方面,开发效率较高,但需要考虑如何优化运行效率。

模式实现

  下面通过Java代码实现一个观察者模式的例子,为了简单起见,我们以订单状态变化为例。

主题接口

package com.example.javaDesignPattern.observer;

/**
 * @author bug菌
 * @version 1.0
 * @date 2023/9/20 14:53
 */
public interface Subject {
    void attach(Observer observer);

    void detach(Observer observer);

    void notifyObservers();
}

具体主题类

package com.example.javaDesignPattern.observer;

import java.util.ArrayList;
import java.util.List;

/**
 * @author bug菌
 * @version 1.0
 * @date 2023/9/20 14:54
 */
class OrderSubject implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private int state;

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
        notifyObservers();
    }

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

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

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(this);
        }
    }
}

观察者接口

package com.example.javaDesignPattern.observer;

/**
 * @author bug菌
 * @version 1.0
 * @date 2023/9/20 14:54
 */
public interface Observer {
    void update(Subject subject);
}

具体观察者类

package com.example.javaDesignPattern.observer;

/**
 * @author bug菌
 * @version 1.0
 * @date 2023/9/20 14:52
 */
public class OrderObserver implements Observer {
    private int state;

    @Override
    public void update(Subject subject) {
        state = ((OrderSubject) subject).getState();
        System.out.println("订单状态发生变化,新状态为:" + state);
    }

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
    }
}

测试用例

package com.example.javaDesignPattern.observer;

/**
 * @author bug菌
 * @version 1.0
 * @date 2023/9/20 14:52
 */
public class ObserverTest {
    public static void main(String[] args) {
        OrderSubject orderSubject = new OrderSubject();
        OrderObserver orderObserver1 = new OrderObserver();
        OrderObserver orderObserver2 = new OrderObserver();
        orderSubject.attach(orderObserver1);
        orderSubject.attach(orderObserver2);

        orderSubject.setState(1);
        orderSubject.detach(orderObserver1);
        orderSubject.setState(0);
    }
}

执行结果如下:

image.png

  在上面的代码中,我们定义了一个Subject接口和一个OrderSubject类来表示主题对象,一个Observer接口和一个OrderObserver类来表示观察者对象。具体来说:

  • OrderSubject实现了Subject接口,包含一个observers列表保存了观察者对象,以及一个state保存了状态信息。当state改变时,我们通过notifyObservers()方法遍历observers列表并调用观察者的update()方法通知它们。
  • OrderObserver实现了Observer接口,保存了一个state变量,当update()方法被调用时会更新state并输出订单状态变化的信息。

  在测试用例中,我们创建了一个OrderSubject对象并添加了两个观察者(OrderObserver)对象。然后我们通过setState()方法改变OrderSubject的状态,这会触发主题对象通知所有观察者对象。最后我们通过detach()方法移除一个观察者对象,再次调用setState()方法,只有剩下的一个观察者对象会收到通知。

代码方法介绍

  • attach(Observer observer):向主题对象中添加一个观察者对象。
  • detach(Observer observer):从主题对象中移除一个观察者对象。
  • notifyObservers():通知所有观察者对象主题对象状态已经改变。
  • update(Subject subject):观察者对象更新对主题对象状态的响应。

书写测试用例

  在前面的实现代码中已经包含了一个简单的测试用例,这里再介绍一下如何编写测试用例。

  我们可以通过断言来判断观察者对象是否得到了正确的通知。下面是一个简单的测试用例。

package observer;

import com.example.javaDesignPattern.observer.OrderObserver;
import com.example.javaDesignPattern.observer.OrderSubject;
import org.junit.Test;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.Assert.assertEquals;

/**
 * @author bug菌
 * @version 1.0
 * @date 2023/9/20 14:57
 */
@SpringBootTest
public class ObserverTest {

    @Test
    public void testObserver() {
        OrderSubject orderSubject = new OrderSubject();
        OrderObserver orderObserver1 = new OrderObserver();
        OrderObserver orderObserver2 = new OrderObserver();
        orderSubject.attach(orderObserver1);
        orderSubject.attach(orderObserver2);

        orderSubject.setState(1);
        assertEquals(orderObserver1.getState(), 1);
        assertEquals(orderObserver2.getState(), 1);

        orderSubject.detach(orderObserver1);
        orderSubject.setState(0);
        assertEquals(orderObserver1.getState(), 1);
        assertEquals(orderObserver2.getState(), 0);
    }
}

  在上面的测试用例中,我们创建了一个OrderSubject对象和两个OrderObserver对象,然后通过attach()方法向OrderSubject对象中添加观察者对象。接着,我们先调用setState()方法改变订单状态,并通过断言来检查两个观察者对象是否得到了正确的通知。最后,我们再通过detach()方法移除一个观察者对象,并再次调用setState()方法,再次通过断言来检查只有剩下的一个观察者对象得到了正确的通知。

测试用例执行结果如下:

image.png

测试用例代码解析

这段代码是一个简单的观察者模式的测试代码。

  1. 首先创建了一个订单主题(OrderSubject)和两个订单观察者(OrderObserver)。

  2. 然后将两个观察者注册到订单主题中,即将它们添加为订单主题的观察者。

  3. 接下来设置订单主题的状态为1,然后断言两个订单观察者的状态均为1。

  4. 之后从订单主题中取消注册一个观察者(orderObserver1),并将订单主题的状态设置为0,然后断言仅有的一个订单观察者(orderObserver2)的状态为0,而另一个订单观察者的状态仍为1,因为它已经被取消注册,不再接收主题的状态更新。

小结

  观察者模式是一种常见的设计模式,可以有效地解耦主题对象和观察者对象之间的关系。在实际开发中,我们可以通过建立基于观察者模式的设计模型来实现复杂的业务逻辑,并通过测试用例来验证基于观察者模式的设计模型的正确性。

附录源码

  如上涉及代码均已上传同步在GitHub,提供给同学们参考性学习。

总结

  本文介绍了观察者模式的基本概念、结构、使用场景、优缺点、实现方法及测试用例等多方面内容。在观察者模式中,主题对象和观察者对象建立了一种松耦合的关系,主题对象状态改变时能够自动通知所有观察者对象做出相应的处理。观察者模式通常用于事件处理、状态监控、消息系统、MVC模式、GUI开发等场景中。观察者模式的优点包括对象状态更新、松耦合和拓展性,缺点包括事件洪泛和循环依赖问题。观察者模式的实现方法包含主题接口、具体主题类、观察者接口和具体观察者类,每个类都实现了相应的方法进行状态更新和通知。对于观察者模式的测试用例,我们可以通过断言来判断观察者对象是否得到了正确的通知。

☀️建议/推荐你

  如果想系统性的全面学习设计模式,建议小伙伴们直接毫无顾忌的关注这个专栏《聊设计模式》,无论你是想提升自己的编程技术,还是渴望更好地理解代码背后的设计思想,本专栏都会为你提供实用的知识和启发,帮助你更好地解决日常开发中的挑战,将代码变得更加优雅、灵活和可维护!

  最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。

  同时欢迎大家关注公众号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板、技术文章Markdown文档等海量资料。

📣关于我

  我是bug菌,CSDN | 掘金 | infoQ | 51CTO 等社区博客专家,历届博客之星Top30,掘金年度人气作者Top40,51CTO年度博主Top12,华为云 | 阿里云| 腾讯云等社区优质创作者,全网粉丝合计15w+ ;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试题、4000G pdf电子书籍、简历模板等海量资料。


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。