Redian新闻
>
观察者模式,超详细!

观察者模式,超详细!

公众号新闻

设计模式提供了软件开发过程中的一些最佳实践,可以帮助我们解决常见的编程问题,提高软件的可维护性和可复用性,并使我们的代码更加健壮和灵活。设计模式可以带来以下好处:提高代码的可读性和可维护性、提高软件的可复用性、提高开发效率、提高系统的灵活性和可扩展性。今天我们讲一下观察者模式的具体应用。

观察者模式是一种软件设计模式,它允许一个对象(称为“主题”)管理其依赖项(称为“观察者”),它定义了对象之间的一对多依赖关系,当一个对象状态发生改变时,其相关依赖项将会自动收到通知。这种模式提供了一种灵活的方式,将一个对象的状态与依赖它的多个对象联系起来。

在观察者模式中,主题和观察者之间建立了一种订阅关系。主题负责维护其状态并提供一个注册表,用于存储与其相关联的观察者对象。当主题的状态发生改变时,它会自动通知所有与之相关联的观察者,并传递相应的参数。观察者接收到通知后,可以执行相应的操作来响应主题状态的改变。

意图

观察者模式是一种行为设计模式 允许你定义一种订阅机制 可在对象事件发生时通知多个 观察 该对象的其他对象

 问题

假如你有两种类型的对象  顾客 商店  顾客对某个特定品牌的产品非常感兴趣 例如最新型号的 iPhone 手机 而该产品很快将会在商店里出售

顾客可以每天来商店看看产品是否到货 但如果商品尚未到货时 绝大多数来到商店的顾客都会空手而归

前往商店和发送垃圾邮件

另一方面 每次新产品到货时 商店可以向所有顾客发送邮件 可能会被视为垃圾邮件 这样 部分顾客就无需反复前往商店了 但也可能会惹恼对新产品没有兴趣的其他顾客

我们似乎遇到了一个矛盾 要么让顾客浪费时间检查产品是否到货 要么让商店浪费资源去通知没有需求的顾客

 解决方案

拥有一些值得关注的状态的对象通常被称为 由于它要将自身的状态改变通知给其他对象 我们也将其称为 publisher 所有希望关注发布者状态变化的其他对象被称为 subscribers

观察者模式建议你为发布者类添加订阅机制 让每个对象都能订阅或取消订阅发布者事件流 不要害怕 这并不像听上去那么复杂 实际上 该机制包括 1 一个用于存储订阅者对象引用的列表成员变量 2 几个用于添加或删除该列表中订阅者的公有方法

订阅机制允许对象订阅事件通知

现在 无论何时发生了重要的发布者事件 它都要遍历订阅者并调用其对象的特定通知方法

实际应用中可能会有十几个不同的订阅者类跟踪着同一个发布者类的事件 你不会希望发布者与所有这些类相耦合的 此外如果他人会使用发布者类 那么你甚至可能会对其中的一些类一无所知

因此 所有订阅者都必须实现同样的接口 发布者仅通过该接口与订阅者交互 接口中必须声明通知方法及其参数 这样发布者在发出通知时还能传递一些上下文数据

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

如果你的应用中有多个不同类型的发布者 且希望订阅者可兼容所有发布者 那么你甚至可以进一步让所有发布者遵循同样的接口 该接口仅需描述几个订阅方法即可 这样订阅者就能在不与具体发布者类耦合的情况下通过接口观察发布者的状态

 真实世界类比

杂志和报纸订阅

如果你订阅了一份杂志或报纸 那就不需要再去报摊查询新出版的刊物了 出版社 即应用中的 发布者 会在刊物出版后 甚至提前 直接将最新一期寄送至你的邮箱中

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

我们看一段代码示例,然后再通过示例进行分析。在JavaScript中,我们可以使用原型或类来实现观察者模式。下面是一个使用原型的实现示例:

// 观察者接口  var Observer = function() {};  
Observer.prototype.update = function(data) {};
// 具体观察者 var ConcreteObserver1 = function() {}; ConcreteObserver1.prototype = Object.create(Observer.prototype); ConcreteObserver1.prototype.constructor = ConcreteObserver1; ConcreteObserver1.prototype.update = function(data) { console.log('ConcreteObserver1 received data: ' + data); };
// 具体观察者 var ConcreteObserver2 = function() {}; ConcreteObserver2.prototype = Object.create(Observer.prototype); ConcreteObserver2.prototype.constructor = ConcreteObserver2; ConcreteObserver2.prototype.update = function(data) { console.log('ConcreteObserver2 received data: ' + data); };
// 主题 var Subject = function() { this.observers = []; };
Subject.prototype.registerObserver = function(observer) { this.observers.push(observer); };
Subject.prototype.notifyObservers = function(data) { for (var i = 0; i < this.observers.length; i++) { this.observers[i].update(data); } };
// 具体主题 var ConcreteSubject = function() {}; ConcreteSubject.prototype = Object.create(Subject.prototype); ConcreteSubject.prototype.constructor = ConcreteSubject; ConcreteSubject.prototype.setState = function(data) { this.notifyObservers(data); };

在上面的代码中,我们首先定义了一个Observer接口,它包含一个update方法。然后我们创建了两个具体的观察者ConcreteObserver1和ConcreteObserver2,它们都实现了Observer接口的update方法。接着我们定义了一个主题Subject,它包含一个观察者数组和一个注册方法registerObserver,以及一个通知方法notifyObservers。最后我们创建了一个具体主题ConcreteSubject,它继承了Subject的原型并实现了一个setState方法,该方法调用通知方法来通知所有观察者状态改变。

在上面的示例中,我们使用了原型继承来实现Observer接口和具体的观察者类。在实际应用中,我们也可以使用类继承或ES6的class语法来实现这些类。另外,在具体主题ConcreteSubject中,我们通过调用notifyObservers方法来通知所有观察者状态改变,这个方法可以传递一个参数作为通知的内容。在具体观察者的update方法中,我们可以根据传递的参数来执行相应的操作。

除了使用JavaScript实现观察者模式外,我们还可以在其他编程语言和框架中找到这种模式的实现。例如,Redis的订阅模型和WebSocket请求都使用了类似的方式来实现主题和观察者之间的订阅关系。这些实现方式都允许客户端订阅特定主题,并在主题状态发生改变时自动接收通知。

观察者模式适合应用场景

 当一个对象状态的改变需要改变其他对象 或实际对象是事先未知的或动态变化的时 可使用观察者模式

 当你使用图形用户界面类时通常会遇到一个问题 比如 你创建了自定义按钮类并允许客户端在按钮中注入自定义代码 这样当用户按下按钮时就会触发这些代码

观察者模式允许任何实现了订阅者接口的对象订阅发布者对象的事件通知 你可在按钮中添加订阅机制 允许客户端通过自定义订阅类注入自定义代码

 当应用中的一些对象必须观察其他对象时 可使用该模式 但仅能在有限时间内或特定情况下使用

 订阅列表是动态的 因此订阅者可随时加入或离开该列表

 实现方式

  1. 仔细检查你的业务逻辑 试着将其拆分为两个部分 独立于其他代码的核心功能将作为发布者 其他代码则将转化为一组订阅类

  2. 声明订阅者接口 该接口至少应声明一个 update方法

  3. 声明发布者接口并定义一些接口来在列表中添加和删除订阅对象 记住发布者必须仅通过订阅者接口与它们进行交互

  4. 确定存放实际订阅列表的位置并实现订阅方法 通常所有类型的发布者代码看上去都一样 因此将列表放置在直接扩展自发布者接口的抽象类中是显而易见的 具体发布者会扩展该类从而继承所有的订阅行为

    但是 如果你需要在现有的类层次结构中应用该模式 则可以考虑使用组合的方式 将订阅逻辑放入一个独立的对象 然后让所有实际订阅者使用该对象

  5. 创建具体发布者类 每次发布者发生了重要事件时都必须通知所有的订阅者

  6. 在具体订阅者类中实现通知更新的方法 绝大部分订阅者需要一些与事件相关的上下文数据 这些数据可作为通知方法的参数来传递

    但还有另一种选择 订阅者接收到通知后直接从通知中获取所有数据 在这种情况下 发布者必须通过更新方法将自身传递出去 另一种不太灵活的方式是通过构造函数将发布者与订阅者永久性地连接起来

  7. 客户端必须生成所需的全部订阅者 并在相应的发布者处完成注册工作

 观察者模式优缺点

  •  优点:

    • 降低目标与观察者之间的耦合关系

    • 支持“广播通信”

    • 符合开闭原则 

  •  确定:

    • 通知可能会花费很长时间

    • 循环依赖的问题

    • 没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的



总结一下,观察者模式适用于任何需要实现一对多依赖关系的场景,使得主题的状态改变可以自动通知给所有的观察者。

  1. 订阅/发布系统:观察者模式可以用于实现订阅/发布系统。主题可以代表各种事件或消息,观察者可以订阅感兴趣的主题并接收相关的通知。

  2. 实时通信:观察者模式可以用于实现实时通信。当某个事件发生时,相关的观察者可以立即得到通知并做出相应的响应。

  3. 数据绑定:在图形用户界面开发中,观察者模式可以用于实现数据绑定。当某个数据源发生改变时,相关的视图可以自动更新。

  4. 事件驱动系统:观察者模式可以用于实现事件驱动系统。当某个事件触发时,相关的观察者可以收到通知并执行相应的操作。

  5. 异步消息处理:在分布式系统中,观察者模式可以用于实现异步消息处理。当某个消息到达时,相关的观察者可以收到通知并处理该消息。

(版权归原作者所有,侵删)


微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
36氪暗涌·2023产业未来大会:做产业投资时代里的机遇洞察者11月CFA考试备考:《CFA冲刺笔记》yyds!精炼+逻辑性强+详细!超详细访校面试攻略!行前准备、着装要求、访校细节…你想知道的都在这里超详细版!顶尖寄宿美高访华行程汇总!抓住和梦校招生官1V1面谈机会!凄美情缘 第二十二章在美国看病、付Daycare学费还能免税!HSA、FSA免税账户超详细科普超详细|帮家人拿绿卡,你要知道的都在这里!全网最新最全!寄宿美高招生官访华行程汇总,含超详细预约指南!凄美情缘 第二十一章2023留学生一、二线城市落户政策超详细汇总2023最新!新移民首次登陆美国超详细指南震惊!澳洲海关再戒严,网购国内零食竟被直接销毁?0元首重,全澳畅邮!这家华人运输公司出手,开启货品护航模式,超低价格使命必达!花一件的钱买到三件!这件美背内衣,一秒A变C,胸美腰细!超详细解答来了!新西兰移民政策重大变革,这些要点务必了解!国内线下 GRE考试报名超详细流程来啦!《CFA冲刺笔记》yyds!精炼+逻辑性强+详细!凄美情缘 第二十四章要打飞的?一定要先看看这份超详细的加航行李要求团购!多伦多最大室内水上乐园超详细攻略~孩子超超超喜欢!【超详细攻略】刚到海外手忙脚乱?留学生落地办银行卡一文详解2023绝不能错过的日本夏日祭,超全玩法一篇搞定,附详细时间表【文末有福利】开学第一课,和观察者网一起看《长安》马上见证历史!2023年大选在即,超详细图文手把手教你如何投票!新西兰的未来就在你手中!超详细的ImageJ教程,一步作图,小白也能快速出图,顺利发表SCI超详细肘倒立教程~假期也要动起来!超详细暑期体育锻炼指南,快分享给家长孩子! | 精选凄美情缘 第二十章从学术小白到老司机:这一超详细的期刊科普送给你别再穿小白鞋了!今夏爆火了一双“熊猫鞋”,透气清爽!无敌显腿细!怎么能讲得这么细!劳动报酬、员工手册设计,按这个做就够了前哨速览:Roblox推出17岁模式,黑客发现特斯拉的「马斯克模式」资深财务总监解读:一个超级经典的公司财务报表案例分析,很详细!怎么能讲得这么细!听了田文昌的课,找到能力“缺口”了[旅游] 自驾拉萨的一万八千里路 「超详细攻略」凄美情缘 第二十三章
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。