首页 > 其他分享 >观察者模式:如何发送消息变化的通知?

观察者模式:如何发送消息变化的通知?

时间:2024-09-14 23:22:32浏览次数:18  
标签:对象 通知 void 观察者 模式 发送 Message public

观察者模式是一种非常流行的设计模式,也常被叫作订阅-发布模式。观察者模式在现代的软件开发中应用非常广泛,比如,商品系统、物流系统、监控系统、运营数据分析系统等。

现在我们常说的基于事件驱动的架构,其实也是观察者模式的一种最佳实践。当我们观察某一个对象时,对象传递出的每一个行为都被看成是一个事件,观察者通过处理每一个事件来完成自身的操作处理。

一、模式原理分析

观察者模式的原始定义是:定义对象之间的一对多依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。

这个定义中包含了两个前提条件:一是被依赖的对象叫作被观察者,依赖的对象叫作观察者;二是观察者观察被观察者的状态变化。不过,这听上去还是有点太抽象,实际上我们更容易理解下面这些不同的对于观察者模式的叫法:

  • 发布者-订阅者;

  • 生产者-消费者;

  • 事件发布-事件监听。

不管怎么叫,这些模式在本质上都是观察者模式。我们通过 UML 图来看看观察者模式中对象之间的关系:

从该 UML 图中,我们能看出观察者模式包含的四个关键角色。

  • 发布者(Publisher):也被叫作主题、被订阅者、被观察者等,通常是指观察者关心的相关对象集合,比如,将 GitLab 上的 Git 库作为发布者,我们关心 Git 库的代码发生了变更后做特定的操作。

  • 具体发布者(PublisherImpl):实现了发布者定义的方法的具体实现类。

  • 订阅者(Observer):也叫作观察者,它会存储一个注册列表,用于存放订阅者。当发布者发布消息或事件时,会通知到订阅者进行处理。

  • 具体订阅者(ObserverImpl):实现具体定义的方法操作。

接下来,再来看看该 UML 对应的代码实现,这是一种比较经典的实现方式。具体代码如下所示:

public interface Publisher {
    void addObserver(Observer o);
    void removeObserver(Observer o);
    void notify(double amt);
}
public class PublisherImpl implements Publisher {
    private String acct;
    private double balance;
    private List<Observer> myObservers;
    public PublisherImpl(String anAcct, double aBalance) {
        acct = anAcct;
        balance = aBalance;
        myObservers = new ArrayList();
    }
    public void addObserver(Observer o){
        myObservers.add(o);
    }
    public void removeObserver(Observer o) {
        myObservers.remove(o);
    }
    public void notify(double amt) {
        balance -= amt;
        if(balance < 0) {
            overdrawn();
        }
    }
    private void overdrawn() {
        for (Observer o: myObservers) {
            o.notify(acct, balance);
        }
    }
}
public interface Observer {
    void notify(String acct, double amt);
}
public class ObserverImpl implements Observer {
    @Override
    public void notify(String acct, double amt) {
        System.out.println("=== 接收到通知:账户:"+acct + " 账单:"+amt);
    }
}
public class Demo {
    public static void main(String[] args) {
        Publisher account = new PublisherImpl("TEST123", 10.00);
        Observer bill = new ObserverImpl();
        account.addObserver(bill);
        account.notify(11.00);
    }
}
//输出结果
=== 接收到通知:账户:TEST123 账单:-1.0

在上面的代码实现中,具体发布者会动态维护一个订阅者的列表:可在运行时根据程序需要开始或停止发布给对应订阅者的事件通知。实际上,发布者本身并不维护订阅列表,它会将工作委派给具体发布者;订阅者在接收到发布者的消息后,会委派具体的订阅者来进行相关的处理

二、使用场景分析

观察者模式常见的使用场景有以下几种。

  • 当一个对象状态的改变需要改变其他对象时。比如,商品库存数量发生变化时,需要通知商品详情页、购物车等系统改变数量。

  • 一个对象发生改变时只想要发送通知,而不需要知道接收者是谁。比如,订阅微信公众号的文章,发送者通过公众号发送,订阅者并不知道哪些用户订阅了公众号。

  • 需要创建一种链式触发机制时。比如,在系统中创建一个触发链,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象……这样通过观察者模式能够很好地实现。

  • 微博或微信朋友圈发送的场景。这是观察者模式的典型应用场景,一个人发微博或朋友圈,只要是关联的朋友都会收到通知;一旦取消关注,此人以后将不会收到相关通知。

  • 需要建立基于事件触发的场景。比如,基于 Java UI 的编程,所有键盘和鼠标事件都由它的侦听器对象和指定函数处理。当用户单击鼠标时,订阅鼠标单击事件的函数将被调用,并将所有上下文数据作为方法参数传递给它。

为了帮助你更好地理解观察者模式的使用,下面我们还是通过一个简单的例子来帮助你理解——实现一个简易的消息订阅通知功能。

我们首先定义观察者 MessageObserver。它只有一个 update 方法,当有消息 Message 发送过来时就会调用该方法。

public interface MessageObserver {
    void update(Message m);
}

接着再定义消息的具体数据结构,这里使用一个 content 消息内容。

public class Message {
    final String content;
    public Message (String m) {
        this.content = m;
    }
    public String getContent() {
        return content;
    }
}

再接下来,实现 Subject 的具体发布者类 MessagePublisher,它持有一组观察者 MessageObserver 实例,可以通过 attach 和 detach 接口方法新增和删除观察者。

public class MessagePublisher implements Subject {
    private List<MessageObserver> observers = new ArrayList<>();
    @Override
    public void attach(MessageObserver o) {
        observers.add(o);
    }
    @Override
    public void detach(MessageObserver o) {
        observers.remove(o);
    }
    @Override
    public void notifyUpdate(Message m) {
        observers.forEach(x->x.update(m));
    }
}

最后,我们实现三个具体订阅者类,它们都实现了 MessageObserver 接口,分别代表不同的消息接收后的对应处理操作。

public class MessageSubscriber1 implements MessageObserver {
    @Override
    public void update(Message m) {
        System.out.println("MessageSubscriber1 :: " + m.getContent());
    }
}
public class MessageSubscriber2 implements MessageObserver {
    @Override
    public void update(Message m) {
        System.out.println("MessageSubscriber2 :: " + m.getContent());
    }
}
public class MessageSubscriber3 implements MessageObserver {
    @Override
    public void update(Message m) {
        System.out.println("MessageSubscriber3 :: " + m.getContent());
    }
}

设计完成后,我们来运行一段单元测试,看看具体运行结果。

public class Client {
    public static void main(String[] args) {
        MessageObserver s1 = new MessageSubscriber1();
        MessageObserver s2 = new MessageSubscriber2();
        MessageObserver s3 = new MessageSubscriber3();
        Subject p = new MessagePublisher();
        p.attach(s1);//
        p.attach(s2);
        p.notifyUpdate(new Message("First Message"));   //s1和s2会收到消息通知
        p.detach(s1);
        p.attach(s3);
        p.notifyUpdate(new Message("Second Message")); //s2和s3会收到消息通知
    }
}
//输出结果
MessageSubscriber1 :: First Message
MessageSubscriber2 :: First Message
MessageSubscriber2 :: Second Message
MessageSubscriber3 :: Second Message

上面的代码实现非常简单,但是充分体现了观察者模式的使用场景。观察者模式使用场景的特点在于找到合适的被观察者,定义一个通知列表,将需要通知的对象放到这个通知列表中,当被观察者需要发起通知时,就会通知这个列表中的所有“人”。这和现实中我们打开收音机收听电台很类似。

三、为什么使用观察者模式?

分析完观察者模式的原理和使用场景后,我们再来说说使用观察者模式的原因,主要有以下两个。

第一个,为了方便捕获观察对象的变化并及时做出相应的操作。 观察者模式对于对象自身属性或行为发生变化后,需要快速应对的应用场景尤其有效,比如,购物包裹的运送。因为商品变成包裹的形式后会出现各种各样的状态变化,比如,配送中、接收中、退货中等,这些状态变化非常快,而每一个状态都会对应一系列连锁的操作,这时使用观察者模式就非常方便,能够通知需要知道这些状态变化的系统,并做相应的处理,比如,物流监控、性能监控、运营系统等。

第二个,为了提升代码扩展性。 如果不使用观察者模式来捕获一个被观察对象的属性变化,那么就需要在被观察对象执行代码逻辑中加入调用通知某个对象进行变更的逻辑,这样不仅增加了代码的耦合性,也让代码扩展变得非常困难,因为要想新增一个新的观察对象,就需要修改被观察对象的代码,这样的扩展性非常低。相反,使用观察者模式则只需要将观察者对象注册到被观察者存储的观察者列表中就能完成代码扩展。

四、观察者模式的优缺点是什么?

通过上述分析,我们也可以得出使用观察者模式主要有以下优点。

  • 能够降低系统与系统之间的耦合性。 比如,建立以事件驱动的系统,我们能创建更多观察者与被观察者,对象之间相互的关系比较清晰,可以随时独立添加或删除观察者。

  • 提升代码扩展性。 由于观察者和被观察之间是抽象耦合,所以增删具体类并不影响抽象类之间的关系,这样不仅满足开闭原则,也满足里氏替换原则,能够很好地提升代码扩展性。

  • 可以建立一套基于目标对象特定操作或数据的触发机制。 比如,基于消息的通知系统、性能监控系统、用户数据跟踪系统等,观察者通过特定的事件触发或捕获特定的操作来完成一系列的操作。

当然,观察者模式也有一些缺点。

  • 增加代码的理解难度。 由于使用组合关系,被观察者和观察者之间属于松散关系,所以在代码逻辑的理解上,就需要提前搞清楚上下文中有哪些中间类或调用逻辑关系,这样才能正确理解逻辑。

  • 降低了系统性能。 观察者模式通常需要事件触发,当观察者对象越多时,被观察者需要通知观察者所花费的时间也会越长,这样会在某种程度上影响程序的效率。

虽然观察者模式在原理上是一个比较抽象的模式,但是业界根据不同的应用场景和需求总结出了很多不同的实现方式,这让观察者模式变得易于使用。

在观察者模式中,被观察者通常会维护一个观察者列表。当被观察者的状态发生改变时,就会通知观察者。观察者模式现在被大量应用在分布式系统的开发中。

从本质上来讲,观察者模式描述了如何建立对象与对象之间一种简单的依赖关系。在现实中,最为常见的观察者模式的例子就是订阅微信公众号,当公众号发布一篇新文章时,我们能在微信上收到消息,然后选择合适的时间打开阅读。

因此,当我们在使用观察者模式时,要重点关注哪些变化是我们需要从被观察对象那里捕获(观察到)并进行下一步处理的,一方面不能盲目捕获所有的变化,另一方面也不能遗漏重要的变化。换句话说,找到合适的变化并进行正确的处理才是使用观察者模式的正确打开方式

文章(专栏)将持续更新,欢迎关注公众号:服务端技术精选。欢迎点赞、关注、转发

个人小工具程序上线啦,通过公众号(服务端技术精选)菜单【个人工具】即可体验,欢迎大家体验后提出优化意见

标签:对象,通知,void,观察者,模式,发送,Message,public
From: https://blog.51cto.com/jiangyi/12019507

相关文章

  • UART——通用异步接收发送器
    数字系统是以1和0的形式共享和存储信息。要与具有不同架构的多个设备共享此信息,我们需要一种通用的数据交换方法。这就是各种通信协议发挥作用的地方,其中之一就是通用异步接收器发送器(UART)。它是嵌入式电子产品中最常用的通信协议之一。它是一种串行、全双工、异步......
  • K8s利用etcd定时备份集群结合钉钉机器人通知
    如何通过脚本的方式进行K8s集群的备份查看K8s中master节点中etcd集群的状态kubectlgetpods-nkube-system|grepetcd由于使用的etcd服务是K8s搭建时自身携带的,并不是独立搭建的etcd集群信息。使用K8s搭建集群时,etcd是Kubernetes集成的一个重要组件因此需要查看此K8s中etc......
  • 无需代码,通过逻辑引擎简单几步配置,实现邮件自动化发送
    无论是在个人生活中还是在工作场景中,发送邮件是一个常见的需求。通常在业务系统中有这样的场景:新增数据的时候动态取一些信息然后发送邮件给客户开户发送密钥邮件等那么在JVS低代码逻辑引擎中,我们可以通过配置【发送邮件】节点来实现配置说明发送邮件,需要有一个邮件传输协议服务器......
  • Python SMTP发送邮件
    使用Python发送HTML格式的邮件Python发送带附件的邮件在HTML文本中添加图片使用第三方SMTP服务发送SMTP(SimpleMailTransferProtocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。python的smtplib提供了一种很方......
  • sipp模拟uas发送reinvite
    概述freeswitch是一款简单好用的VOIP开源软交换平台。在更新了sipp模拟update的配置方案之后,我希望对比一下fs对update和reinvite的处理流程。本文档记录sipp的配置方案,该方案中包含了update和reinvite的信令。环境CentOS7.9freeswitch1.10.7sipp.3.6.2方案描述测试......
  • 选择生产通知单还是生产计划单
    选择生产通知单还是生产计划单,取决于企业的生产管理模式、流程复杂度和资源配置方式。两者各有优缺点,以下是一些考虑因素,可以帮助你决定哪个更适合你的业务需求:生产通知单优点:更灵活的准备时间:可以提前通知生产部门,给他们更多时间准备原材料、设备和人力资源。更好地协......
  • sms4j 发送短信
    一、使用介绍技术介绍:SMS4J:短信支持阿里云腾讯云云片等等各式各样的短信服务商项目地址:SMS4J:让简单的事情回归简单的本质。SMS4J为短信聚合框架,帮您轻松集成多家短信服务,解决接入多个短信SDK的繁琐流程。目前已接入数家常见的短信服务商,后续将会继续集成。后续......
  • Python中的观察者模式:从入门到精通
    引言观察者模式允许对象(称为“观察者”)注册到另一个对象(称为“主题”或“被观察者”),从而在主题状态改变时自动收到通知。这种机制使得多个观察者可以独立地响应同一个事件,增强了系统的灵活性和可维护性。特别是在构建高度解耦、易于扩展的应用程序时,观察者模式显得尤为重要。基础......
  • urllib发送get请求_中文传参问题
    GET请求是HTTP协议中的一种基本方法,当需要在GET请求中传递中文参数时需要额外对中文进行编码(英文不需要),因为url中只能包含ascii字符。可以使用urllib.parser.urlencode()或urllib.parse.quote()方法对中文转码。详细查官方文档:https://docs.python.org/3.12/library/urllib.par......
  • Qml 实现仿前端的 Notification (悬浮出现页面上的通知消息)
    【写在前面】经常接触前端的朋友应该经常见到下面的控件:在前端中一般称它为Notification或 Message,但本质是一种东西,即:悬浮弹出式的消息提醒框。这种组件一般具有以下特点:1、全局/局部显示:它不依赖于具体的页面元素,可以在整个页面的任意位置显示。2、自动消失:默认情况下,......